# React 事件绑定原理

# 今日解题

React 并不是将 click 事件绑定在该 div 的真实 DOM 上,而是在 document 处监听所有支持的事件,当事件发生并冒泡至 document 处时,React 将事件内容封装并交由真正的处理函数运行。这样的方式不仅减少内存消耗,还能在组件挂载销毁时统一订阅和移除事件。
另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不需要事件冒泡的话,调用event.stopPropagation是无效的,而应该调用event.preventDefault

div ---事件冒泡至顶层---> 监听 document 事件 ----->实例化成统一的ReactEvent事件【SyntheticEvent】---->event对象交由对应的处理器执行【handle1, handle2....】

# 具体的几个阶段

    1. 事件注册
    1. 事件存储
    1. 事件触发执行
    1. 合成事件

# 事件注册

  • 组件转载/更新【_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 中取出回调函数
  • 返回带有合成事件参数的回调函数

2021.01.28