面试题
什么是合成事件(事件代理),与原生事件有什么区别?

什么是合成事件(事件代理),与原生事件有什么区别?

React中所有触发的事件,都在React实现的一个合成事件层。作用是

  • 规范化了这些接口在不同浏览器中的行为,抹平了不同浏览器事件对象之间的差异,解决了浏览器兼容的问题,方便事件统一管理;
  • 避免垃圾回收;react事件对象不会被释放掉,而是存放进一个数组中,避免频繁地去创建和销毁(垃圾回收)。

在 React 初始化渲染的时候,ReactDOM.render 会调用函数 listenToAllSupportedEvents 来绑定事件。 不支持冒泡事件的,React 会直接把事件绑定在具体的元素上。

对合成事件阻止不会影响原生事件的执行。

在React底层,主要对合成事件做了两件事:

  • 事件委派: React会把所有的事件绑定到结构的最外层(Virtual DOM根节点),使用统一的事件监听器, 这个事件监听器上维持了一个映射来保存所有组件内部事件监听和处理函数。 react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用preventDefault()来阻止默认行为。
  • 自动绑定: React组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件。

从v17.0.0开始, React 不会再将事件处理添加到 document 上, 而是将事件处理添加到渲染 React 树的根 DOM 容器中。

React 代码执行时,顶层会自动执行事件的注册,初始化事件插件。 React 首次渲染时,会在根节点上绑定所有原生事件。支持冒泡的事件,React 会同时绑定捕获阶段和冒泡阶段的事件;不支持冒泡的事件,会将事件绑定在具体 DOM 元素上。 事件触发前会从目标元素的 Fiber 节点向上收集同类型事件队列,构造合成对象,同类型的事件会复用同一个合成事件实例对象。 根据监听的事件阶段,决定顺序还是倒序遍历执行事件处理函数(模拟事件的冒泡捕获机制)。

  • 事件注册:registerEvents;将80种事件类型添加到allNativeEvents的Set数据结构中。
  • 事件监听:listenToAllSupportedEvents;遍历上面事件注册添加到allNativeEvents的事件。 监听的listener是一个事件派发器,并不是真实的浏览器事件或你写的事件回调函数。
  • 事件派发:dispatchEvent;事件一旦在id = root的 DOM 元素中委托,其实是一直在触发的, 根据消息的数据找到真正触发事件的 DOM 元素,根据fiber节点的类型以及是否已渲染来决定是否要派发事件。
  • 事件合成:SyntheticBaseEvent;当我们点击页面的某个元素时,React会根据原生事件nativeEvent找到触发事件的 DOM 元素 和对应的fiber节点。并以该节点为孩子节点往上查找,找到包括该节点及以上所有的click回调函数创建dispatchListener, 并添加到listeners数组中。同类型的事件会复用同一个合成事件实例对象。

react 默认事件代理的方式,实际上没有任何冒泡的过程,是程序自己模拟冒泡的操作。 当真实dom触发后冒泡到document后才会对react事件进行处理。

总结合成事件与原生事件执行顺序:

  • 合成事件不管冒泡阶段还是捕获阶段,都要晚于原生事件冒泡阶段
  • 不管合成事件还是原生事件,冒泡阶段都要晚于捕获阶段

因为事件监听绑定在dom上,并且在冒泡到dom才会执行,所以原生的阻止会阻断合成事件的执行, 因为都到不了document。 同理,合成事件的阻断不会影响原生事件,因为原生事件已经执行完毕了。