React基于浏览器事件机制
实现了一套自己的事件机制,包括:事件注册
、事件合成
、事件冒泡
、事件触发
等。
React的事件并没有绑定到具体的dom节点上,而是绑定在了document上,然后由统一的事件监听器去监听事件的触发
React在内部维护了一个映射表来记录事件与组件的事件处理函数的对应关系。当某个事件触发时,React根据映射表将时间分派给指定的事件处理函数。当一个组件挂载与卸载时,相应的事件处理函数会自动被添加到事件监听器的内部映射表中或从表中删除。这样做简化了事件处理和回收机制,效率也提升很大。
合成事件是React模拟DOM原生事件的一个事件对象,这些合成事件并没有绑定到对应的真实DOM
上,而是通过事件代理
的方式,将所有的事件绑定到了document
上。其优点如下:
SyntheticEvent
是React合成事件的基类,定义了合成事件的基础公共属性和方法。
React会根据当前的事件类型来使用不同的合成事件对象,比如单击事件SyntheticMouseEvent
,焦点事件SyntheticFocusEvent
等,这些事件都继承SyntheticEvent
。
SyntheticEvent
(负责所有事件合成)dispatchEvent
将封装的事件内容交由真正的处理函数执行在React组件挂载阶段,根据组件内声明的事件类型(onClick、onChange等),在document上通过addEventListener
注册事件,并指定统一的回调函数dispatchEvent
。
在document上不管注册什么事件,都具有统一的回调函数dispatchEvent
。所以对于同一种事件类型,不论在document上注册了几次,最终也只会保留一个有效实例,这样就可以减少内存消耗。
function TestComponent() {
handleFatherClick=()=>{
// ...
}
handleChildClick=()=>{
// ...
}
return <div className="father" onClick={this.handleFatherClick}>
<div className="child" onClick={this.handleChildClick}>child </div>
</div>
}
在上述代码中,事件类型是onClick
,由于React事件代理机制,会指定统一的回调函数dispatchEvent
,所以最终只会在document
上保留一个click
事件,类似document.addEventListener('click', dispatchEvent)
,可以看出React的事件是在DOM事件流的冒泡阶段被触发执行。
React为了在触发事件时可以查到对应的回调去执行,会把组件内的所有事件统一存放到一个对象中(映射表)。首先根据事件类型分类存储,例如click
事件相关的统一存储到一个对象中,回调函数的存储采用键值对的形式,key代表组件的唯一标识,value对应的就是事件的回调函数。
React把所有事件和事件类型以及React组件进行了关联,在事件触发的时候根据当前的组件id
与事件类型
找到对应的回调函数
。
listenerBank
(映射表)中React的事件触发只会发生在DOM事件流的冒泡阶段,因为在document
上注册时就默认是在冒泡阶段被触发执行。
其大致流程如下:
document
时,触发统一的事件回调函数ReactEventListener.dispatchEvent
listenerBank
查找事件回调并合成到event
handleFatherClick=(e)=>{
console.log('father click');
}
handleChildClick=(e)=>{
console.log('child click');
}
render(){
return <div className="box">
<div className="father" onClick={this.handleFatherClick}> father
<div className="child" onClick={this.handleChildClick}>child </div>
</div>
</div>
}
当我们点击child div
时,会同时触发father
的click
事件。
在点击了child div
时,浏览器会捕获到这个事件,然后经过冒泡,事件被冒泡到document
上,交给统一事件处理函数dispatchEvent
对事件进行分发,根据之前在listenerBank
存储的键值对找到触发事件的组件,获取到触发这个事件的元素,遍历这个元素的所有父元素,依次对每一级元素进行处理。构造合成事件,将每一级的合成事件存储在eventQueue
事件队列中,然后批量执行
存储的回调函数。
事件的执行顺序为原生事件先执行,合成事件再执行。合成事件会冒泡到document上,所以尽量避免原生事件和合成事件混用。如果原生事件阻止冒泡,那么就会导致合成事件不执行。
react合成事件不能直接采用return false
的方式来阻止浏览器的默认行为,而必须明确调用event.preventDefault()
来阻止默认行为。
不能使用event.stopPropagation()
来阻止React事件冒泡,必须调用event.preventDefault()
来阻止React事件冒泡。
return false
或 event.stopPropagation()
来阻止冒泡;React事件只能过event.preventDefault()
不能。在包含回调函数的当前事件循环执行完后,所有的event属性都会失效。
function handleClick(event) {
console.log(event.type) // => 有值
setTimeout(function() {
console.log(event.type) // => null
}, 0)
this.setState({
clickEvent: event // => null,因为在回调函数当前事件循环执行完后,所有event会变成null
})
this.setState){
clickEventType: event.type // => 有值
}
}
不是。「几乎」所有事件都代理到了 document,说明有例外,比如audio、video标签的一些媒体事件(如 onplay、onpause 等),是 document 所不具有,这些事件只能够在这些标签上进行事件进行代理,但依旧用统一的入口分发函数(dispatchEvent)进行绑定。