1、React 事件系统是如何工作的

当事件在具体的 DOM 节点上被触发后,最终都会冒泡到 document 上,document 上所绑定的统一事件处理程序会将事件分发到具体的组件实例。

在分发事件之前,React 首先会对事件进行包装,把原生 DOM 事件包装成合成事件

2、React 合成事件

合成事件是 React 自定义的事件对象,在底层抹平了不同浏览器的差异,在上层面向开发者暴露统一的、稳定的、与 DOM 原生事件相同的事件接口。解决了兼容性问题;

虽然合成事件并不是原生 DOM 事件,但它保存了原生 DOM 事件的引用。当你需要访问原生 DOM 事件对象时,可以通过合成事件对象的 e.nativeEvent 属性获取到它;

3、React 事件系统工作流拆解

3.1 事件的绑定

事件的绑定是在组件的挂载过程中完成的,具体来说,是在 completeWork 中完成的。逻辑如下:
image.png

从图中可以看出,事件的注册过程是由 ensureListeningTo 函数开启的。在 ensureListeningTo 中,会尝试获取当前 DOM 结构中的根节点(这里指的是 document 对象),然后通过调用 legacyListenToEvent,将统一的事件监听函数注册到 document 上面。

listenerMap 是在 legacyListenToEvent 里创建/获取的一个数据结构,它将记录当前 document 已经监听了哪些事件。

若事件系统识别到 listenerMap.has(topLevelType) 为 true,也就是当前这个事件 document 已经监听过了,那么就会直接跳过对这个事件的处理,否则才会进入具体的事件监听逻辑。如此一来,即便我们在 React 项目中多次调用了对同一个事件的监听,也只会在 document 上触发一次注册。

为什么针对同一个事件,即便可能会存在多个回调,document 也只需要注册一次监听?因为 React最终注册到 document 上的并不是某一个 DOM 节点上对应的具体回调逻辑,而是一个统一的事件分发函数。

结论:绑定到 document 上的这个统一的事件分发函数,其实就是 dispatchEvent。

3.2 事件的触发

事件触发流程如下:
image.png

4、React 事件系统设计动机

合成事件符合W3C规范,在底层抹平了不同浏览器的差异(兼容性),在上层面向开发者暴露统一的、稳定的、与 DOM 原生事件相同的事件接口。开发者们由此便不必再关注烦琐的底层兼容问题,可以专注于业务逻辑的开发。

自研事件系统使 React 牢牢把握住了事件处理的主动权更加符合自己场景,比如说它想在事件系统中处理 Fiber 相关的优先级概念,或者想把多个事件揉成一个事件(比如 onChange 事件),原生 DOM 会帮它做吗?不会,因为原生讲究的就是个通用性。通过自研事件系统,React 能够从很大程度上干预事件的表现,使其符合自身的需求。