Preface
最近在代码中遇到了react合成事件的相关问题,本文做下记录。
合成事件(SyntheticEvent)
在react中,事件处理程序将传递 SyntheticEvent
的实例,这是一个跨浏览器原生事件包装器。 它具有与浏览器原生事件相同的接口,包括 stopPropagation()
和 preventDefault()
,除了事件在所有浏览器中他们工作方式都相同。
如果您发现由于某种原因需要底层浏览器事件,只需使用 nativeEvent
属性来获取它。 每个 SyntheticEvent
对象都具有以下属性:
类型 | 属性 |
---|---|
boolean | bubbles |
boolean | cancelable |
DOMEventTarget | currentTarget |
boolean | defaultPrevented |
number | eventPhase |
boolean | isTrusted |
DOMEvent | nativeEvent |
void | preventDefault() |
boolean | isDefaultPrevented() |
void | stopPropagation() |
boolean | isPropagationStopped() |
DOMEventTarget | target |
number | timeStamp |
string | type |
异步事件
SyntheticEvent
对象是通过合并得到的。这意味着在事件回调被调用后,SyntheticEvent
对象将被重用并且所有属性都将被取消。 这是出于性能原因。 因此,您无法以异步方式访问该事件。
如果要以异步方式访问事件属性,应该对事件调用 event.persist() ,这将从池中删除合成事件,并允许用户代码保留对事件的引用。
1 | clickHandle = (e) => { |
合成事件机制
react
的所有事件都挂载在document
中。当真实dom
触发后冒泡到document
后才会对react
事件进行处理,所以原生的事件会先执行,然后执行react
合成事件,最后执行真正在document
上挂载的事件。
react
事件机制分为三个部分:
事件注册部分,所有的事件都会注册到document
上,拥有统一的调函数dispatchEvent
来执行事件分发。React
使用对象池来管理合成事件对象的创建和销毁,这样减少了垃圾的生成和新对象内存的分配,大大提高了性能。也就是说不同的事件,可能会共享一个合成事件对象。
触发document
注册原生事件的回调dispatchEvent
,获取到触发这个事件最深一级的元素,遍历这个元素的所有父元素,依次对每一级元素进行处理。构造合成事件。将每一级的合成事件存储在eventQueue
事件队列中。遍历eventQueue
。通过isPropagationStopped
判断当前事件是否执行了阻止冒泡方法。如果阻止了冒泡,停止遍历,否则通过executeDispatch
执行合成事件。释放处理完成的事件。
事件分发部分,首先生成合成事件,注意同一种事件类型只能生成一个合成事件Event
,如onclick
这个类型的事件,dom
上所有带有通过jsx
绑定的onClick
的回调函数都会按顺序(冒泡或者捕获)会放到Event._dispatchListeners
这个数组里,后面依次执行它。也就是说,React
以队列的方式,从触发事件的组件向父组件回溯,调用它们在JSX中声明的callback
,React
自身实现了一套事件冒泡机制。
事件存储部分,合成事件以对象池的方式实现创建和销毁,大大提高了性能。