# React 事件绑定原理
# 今日解题
React 并不是将 click 事件绑定在该 div 的真实 DOM 上,而是在 document 处监听所有支持的事件,当事件发生并冒泡至 document 处时,React 将事件内容封装并交由真正的处理函数运行。这样的方式不仅减少内存消耗,还能在组件挂载销毁时统一订阅和移除事件。
另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不需要事件冒泡的话,调用event.stopPropagation
是无效的,而应该调用event.preventDefault
。
div ---事件冒泡至顶层---> 监听 document 事件 ----->实例化成统一的ReactEvent事件【SyntheticEvent】---->event对象交由对应的处理器执行【handle1, handle2....】
# 具体的几个阶段
- 事件注册
- 事件存储
- 事件触发执行
- 合成事件
# 事件注册
- 组件转载/更新【_updateDOMProperties】
- 通过 lastProps、nextProps 判断是否新增、删除事件分别调用事件注册、卸载方法
- 调用 EventPluginHub 的 enqueuePutListener 进行事件存储
- 获取 document 对象
- 根据事件名称(如:onClick、onCaptureClick)判断是进行冒泡还是捕获
- 判断是否存在 addEventListener 方法,否则使用 attachEvent(兼容 IE)
- 给 document 注册原生事件回调为 dispatchEvent
# 事件存储
- EventPluginHub 负责管理 React 合成事件的 callback,它将 callback 存储在 ListenerBank 中,另外还存储了负责合成事件的 Plugin。
- EventPluginHub 的 putListener 方法是向存储容器中增加一个 listener
- 获取绑定事件元素的唯一标识 Key
- 将 callback 根据事件类型,元素的唯一标识 key 存储在 listenerBank 中
- listenerBank 的结构是:
listenerBank[registrationName][key]
{
onClick: {
nodeid1: ()=>{...}
nodeid2: ()=>{...}
},
onChange: {
nodeid3: ()=>{...}
nodeid4: ()=>{...}
}
}
# 事件触发
- 触发 document 注册原生事件的回调 dispatchEvent
- 获取到触发这个事件最深一级的元素
【这里的事件执行利用了 React 的批处理机制】
<div onClick={this.parentClick} ref={(ref) => (this.parent = ref)}>
<div onClick={this.childClick} ref={(ref) => (this.child = ref)}>
test
</div>
</div>
// -1 首先会获取到this.child
// -2 遍历这个元素的所有父元素,依次对每一级元素进行处理
// -3 构造合成事件
// -4 将每一级合成事件存储在eventQueue事件队列中
// -5 遍历eventQueue
// -6 通过isPropagationStopped判断当前事件是否执行了阻止冒泡的方法
// -7 如果阻止了冒泡,停止遍历,否则通过executeDispatch执行合成事件
// -8 释放处理完的事件
# 合成事件
- 调用 EventPluginHub 的 extractEvents 方法
- 循环所有类型的 EventPlugin(用来处理不同事件的工具方法)
- 在每个 EventPlugin 中根据不同的事件类型,返回不同发事件池
- 在事件池中取出合成事件,如果事件池是空,那么创建一个新的
- 根据元素 nodeid(唯一标识 key)和事件类型从 listenerBink 中取出回调函数
- 返回带有合成事件参数的回调函数