17 | React 事件机制

  无论是在面试场景下,还是在实际的开发中,React 事件相关的问题都更倾向于考验我们对事件工作流、事件特征等逻辑层面问题的理解,而非对源码细节的把握。所以掌握事件工作流、事件特征等逻辑层面的“主要矛盾”就可以。

回顾原生 DOM 下的事件流

W3C 标准约定了一个事件的传播过程要经过以下 3 个阶段:

  1. 事件捕获阶段

  2. 目标阶段

  3. 事件冒泡阶段

在原生 DOM 中,事件委托(也叫事件代理)是一种重要的性能优化手段。利用事件的冒泡特性,把多个子元素的同一类型的监听逻辑,合并到父元素上通过一个监听函数来管理的行为,就是事件委托。通过事件委托,我们可以减少内存开销、简化注册步骤,大大提高开发效率。这绝妙的事件委托,正是 React合成事件的灵感源泉。

React 事件系统是如何工作的

当事件在具体的 DOM 节点上被触发后,最终都会冒泡到 document 上,document 上所绑定的统一事件处理程序会将事件分发到具体的组件实例。在分发事件之前,React 首先会对事件进行包装,把原生 DOM 事件包装成合成事件。

认识 React 合成事件

合成事件是 React 自定义的事件对象,它符合W3C规范,在底层抹平了不同浏览器的差异,在上层面向开发者暴露统一的、稳定的、与 DOM 原生事件相同的事件接口。虽然合成事件并不是原生 DOM 事件,但它保存了原生 DOM 事件的引用。当你需要访问原生 DOM 事件对象时,可以通过合成事件对象的 e.nativeEvent 属性获取到它。

React 事件系统工作流拆解

事件的绑定是在组件的挂载过程中完成的,具体来说,是在 completeWork 中完成的。completeWork 内部有三个关键动作:创建 DOM 节点(createInstance)、将 DOM 节点插入到 DOM 树中(appendAllChildren)、为 DOM 节点设置属性(finalizeInitialChildren)。

其中“为 DOM 节点**设置属性”**这个环节,会遍历 FiberNode 的 props key。当遍历到事件相关的 props 时,就会触发事件的注册链路。工作流如下:

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

最后绑定到 document 上的这个统一的事件分发函数,其实就是 dispatchEvent,针对同一个事件,即便可能会存在多个回调,document 也只需要注册一次监听,比如说我尝试监听的是一个点击事件,那么 topLevelType 的值就会是 click,如下若事件系统识别到 listenerMap.has(topLevelType) 为 true,也就是当前这个事件 document 已经监听过了,那么就会直接跳过对这个事件的处理,否则才会进入具体的事件监听逻辑。如此一来,即便我们在 React 项目中多次调用了对同一个事件的监听,也只会在 document 上触发一次注册。

那么 dispatchEvent 是如何实现事件分发的呢?

事件触发的本质是对 dispatchEvent 函数的调用,直接来看核心工作流,请看下图:

1-3步如上介绍
4.循环收集符合条件的父节点,存进 path 数组中。以当前节点(触发事件的目标节点)为起点,不断向上寻找 父节点,并将这些节点按顺序收集进 path 数组中
5.模拟事件在捕获阶段的传播顺序,收集捕获阶段相关的节点实例与回调函数。会从后往前遍历 path 数组,模拟事件的捕获顺序,收集事件在捕获阶段对应的回调与实例。从后往前遍历 path 数组,其实就是从父节点往下遍历子节点,直至遍历到目标节点的过程,这个遍历顺序和事件在捕获阶段的传播顺序是一致的
6.模拟事件在冒泡阶段的传播顺序,收集冒泡阶段相关的节点实例与回调函数。捕获阶段的工作完成后,traverseTwoPhase 会从前往后遍历 path 数组,模拟事件的冒泡顺序,收集事件在捕获阶段对应的回调与实例。

React 事件系统的设计动机是什么?

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

自研事件系统使 React 牢牢把握住了事件处理的主动权,通过自研事件系统,React 能够从很大程度上干预事件的表现,使其符合自身的需求。比如说它想在事件系统中处理 Fiber 相关的优先级概念,或者想把多个事件揉成一个事件(比如 onChange 事件)

React 合成事件虽然承袭了事件委托的思想,但它的实现过程比传统的事件委托复杂太多。至于 React 事件是否比不使用事件委托的原生 DOM 事件性能更好?没有严格的对比和大量测试数据做支撑,我们很难下结论。

posted @ 2022-12-30 18:00  哥哦狗子  阅读(383)  评论(0编辑  收藏  举报