yaofly2012 / note

Personal blog
https://github.com/yaofly2012/note/issues
44 stars 5 forks source link

Reactjs-Event System #96

Open yaofly2012 opened 4 years ago

yaofly2012 commented 4 years ago

一、概念:

  1. Event System
  2. SyntheticEvent
  3. Event Pool

二、Event System

React内部实现了自己的事件系统,磨平各浏览器的差异,提供统一且和DOM Event一致的访问API。

2.1 事件的捕获阶段和冒泡阶段

  1. React 事件系统也存在事件捕获和冒泡阶段;
  2. 捕获和冒泡的传播(propagation)是沿着React组件节点进行的,跟组件渲染的DOM结构没有关系,Protals: Event Bubbling Through Portals章节有说明。 调用e.stopPropagation方法可以阻止捕获和冒泡阶段中当前事件的进一步传播。
  3. 事件处理函数的实参eventPhase始终是3; 说明React是借助DOM的冒泡事件处理React捕获阶段事件处理函数的。

2.2 绑定捕获阶段事件处理函数

事件处理函数默认在冒泡阶段触发,在事件名称添加后缀Capture表示绑定捕获阶段事件处理函数。

class Index extends React.Component {

    handleClick = (e) => {
        debugger
        console.log(`handleClick -> ${e.currentTarget.id}`);
    }

    handleCaptureClick = (e) => {
        console.log(`handleCaptureClick -> ${e.currentTarget.id}`);
    }

    render() {
        return (
            <div id="wrap" onClick={this.handleClick} onClickCapture={this.handleCaptureClick}>
                <h2>SynthenticEvent</h2>
                <ul>
                    <li id="list-item" onClick={this.handleClick} onClickCapture={this.handleCaptureClick}>Event System</li>
                    <li>Event Pool</li>
                </ul>
            </div>
        )
    }
}

export default Index;

2.3 Focus Event

  1. These focus events work on all elements in the React DOM, not just form elements

    这句话是指所有的React元素都可以绑定focus, blur事件。但注意并不是说所有React元素都会触发focus, blur事件。

  2. 和DOM的focus, blur事件不一样的是React DOMfocus, blur事件会进行冒泡; 行为跟focusin, focusout类似了。
  3. relatedTarget的取值逻辑跟DOM FocusEvent.relatedTarget一样.

class Index extends React.Component { handleFocus = (e) => { console.log(focus:\tcurrentTarget#${e.currentTarget.id}\ttarget#${e.target.id}\t relatedTarget#${e.relatedTarget ? e.relatedTarget.id : 'null'}) } handleBlur = (e) => { console.log(blur:\tctcurrentTarget#${e.currentTarget.id}\ttarget#${e.target.id}\t relatedTarget#${e.relatedTarget ? e.relatedTarget.id : 'null'}) }

render() {
    return (
        <div id="wrap" onBlur={this.handleBlur} onFocus={this.handleFocus}>
            <h2 onClick={this.handleTouchEnd} >SynthenticEvent</h2>
            <ul>
                <li onClick={this.handleClick}>Event System</li>
                <li onClick={this.handleClick}>Event Pool</li>
            </ul>
            <input id="name" onBlur={this.handleBlur} onFocus={this.handleFocus} placeholder="name"/>
            <input id="age" onBlur={this.handleBlur} onFocus={this.handleFocus} placeholder="age"/>
        </div>
    )
}

}

export default Index;


当input触发`focus`事件时,`div#wrap`绑定的`onFocus`事件处理函数也会触发。

## 2.4 Issues
1. class组件的回调函数的this为啥不能特殊处理绑定到组件实例对象上?
我整理了一些原因:
- 事件名称属性取值本质上可以是任意值为函数表达式,这个值很难判断一定就是class组件定义的;
- 函数组件不存在这样的需求

# 三、SyntheticEvent
1. a cross-browser wrapper around the browser’s native event;
2. React事件处理函数实参(事件对象),跟DOM事件对象的属性基本一致。
`SyntheticEvent`对象的属性只和DOM有关,跟React组件已经没有关系了。

## currentTarget和target属性
跟DOM Event对象类似:
- currentTarget 表示当前绑定事件处理函数DOM元素
- target 表示触发事件的DOM元素

# 四、参考
1. [Reactjs Doc handing events](https://reactjs.org/docs/handling-events.html)
2. [Reactjs API SynthenicEvent](https://reactjs.org/docs/events.html)
yaofly2012 commented 4 years ago

一、Event Pooling(事件池)

1.1 复用SyntheticEvent对象

为了提升性能,React会复用SyntheticEvent对象,即事件回调函数的实参是同一个对象(但属性值可能不一样)

let eventObj = false;

function dispalyProps(obj) {
    Object.keys(obj).forEach(key => {
        console.log(`${key}=${obj[key]}`)
    })
}
class Index extends React.Component {

    handleClick = (e) => {
        if(eventObj === false) {
            eventObj = e;
        }
        console.log(e === eventObj) // true
        dispalyProps(e)
        setTimeout(() => {
            dispalyProps(e)
        }, 0)
    }

    handleTouchEnd = (e) => {
        console.log(e === eventObj) // false
    }

    render() {
        return (
            <div>
                <h2 onTouchEnd={this.handleTouchEnd}>SynthenticEvent</h2>
                <ul>
                    <li onClick={this.handleClick}>Event System</li>
                    <li onClick={this.handleClick}>Event Pool</li>
                </ul>
            </div>
        )
    }
}

export default Index;
  1. 当事件处理函数执行完后,事件对象的属性都会被nullified(数据属性),即被重置为null; 这也导致不能在异步操作里访问事件对象(属性都为null了),也不要持久化事件对象后续使用。
  2. 测试发现事件池是以事件类型(eventType)为key来缓存SyntheticEvent对象的,即相同的事件类型的事件处理函数的实参一样。
  3. 复用必有原因的,从另一方面也说明了SynthenicEvent对象创建比较耗时,得看源码了?

1.2 脱离事件池

调用e.persist方法可以让React不缓存当前SyntheticEvent对象:

  1. 事件处理函数执行完后不会对SyntheticEvent对象进行nullify,这样可以在异步操作里可以正确访问事件对象;
  2. 下次再触发该事件时就会创建新的对象。

let eventObj = false;

function dispalyProps(obj) { Object.keys(obj).forEach(key => { console.log(${key}=${obj[key]}) }) } class Index extends React.Component {

handleClick = (e) => {
    if(eventObj === false) {
        eventObj = e;
    } else {
                    console.log(e === eventObj) // false,每次都是新对象
            }

    e.persist(); // 脱离事件池
    dispalyProps(e)
    setTimeout(() => {
        dispalyProps(e)
    }, 0)
}

handleTouchEnd = (e) => {
    console.log(e === eventObj) // false
}

render() {
    return (
        <div>
            <h2 onClick={this.handleTouchEnd}>SynthenticEvent</h2>
            <ul>
                <li onClick={this.handleClick}>Event System</li>
                <li onClick={this.handleClick}>Event Pool</li>
            </ul>
        </div>
    )
}

}

export default Index;



# 二、参考
1. [React API SynthenicEvent](https://reactjs.org/docs/events.html)
2. [What is event pooling in react?](https://stackoverflow.com/questions/36114196/what-is-event-pooling-in-react)
3. [Medium ReactJS Events: “Pooling”, “Nullification”, & event.persist()](https://medium.com/@brunogarciagonzalez/reactjs-events-exploration-a295505016f1)
Vsnoy commented 2 years ago

React17之后移除事件池了