合成事件

2 阅读3分钟

简述

React基于浏览器的事件机制自身实现了一套事件机制,包括事件注册、事件合成、事件派发等,这套事件机制被称为 合成事件;

实现机制

合成事件主要做了两件事:事件委托和自动绑定

事件委托:

React并不会把事件处理函数直接绑定到真实的节点上,而是把所有事件绑定到结构的最外层,使用统一的事件监听器(事件池)来管理,这个事件池上维持了一个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或者卸载时,只是在事件池上插入或删除一些对象,不会频繁的创建和销毁事件对象。 当事件发生时,会通过事件冒泡机制触及顶层节点,通过事件池在映射里找到真正的事件处理函数并调用,这样做简化了事件处理和回收机制,可以大幅度提升效率。

自动绑定:(?)

在 React 组件中,每个方法的上下文都会指向该组件的实例,即自动绑定 this 为当前组件。 而且 React 还会对这种引用进行缓存,以达到 CPU 和内存的最优化。 在使用类组件或者纯函数时,我们需要手动实现 this 的绑定。

问题

问题一:合成事件与原生事件的执行顺序是什么样的

React17后:

  1. window/document/html/body 原始捕获
  2. react 捕获
  3. target 原始捕获
  4. target 原始冒泡
  5. react 冒泡
  6. window/document/html/body 原始冒泡

React17前:

  1. window/document/html/body 原始捕获
  2. 原始捕获
  3. 原始冒泡
  4. React捕获
  5. React 冒泡
  6. react 捕获
  7. window/document/html/body 原始冒泡

问题二:对于同一个 DOM 分别绑定原生事件、合成事件,在原生事件中阻止事件冒泡为什么会阻止合成事件的执行?

合成事件是事件委托的一种实现,主要利用事件冒泡机制将所有事件在 document/root 上统一处理,根据事件流,事件执行顺序为 捕获阶段、目标阶段、冒泡阶段,当我们在原生事件上阻止事件冒泡,那么事件就无法冒泡到 document/root,那么合成事件自然无法执行。

问题三:为什么 React 合成事件都委托在 document 或者 root 根节点 上?

  1. 减少频繁的事件注册,减少内存消耗,提高性能。
  2. 统一处理,并提供合成事件对象,抹平浏览器的兼容性差异

问题四:为什么 React 17 以前是委托在 document,17 以后是委托在 root 根节点?

  1. 性能提升:将事件处理附加到 document 上会导致每个事件都在整个文档范围内冒泡,增加了浏览器的事件捕获和处理的开销。尤其是在应用比较大、包含很多 DOM 节点时,这种做法会影响性能。而将事件处理绑定到 React 渲染树的根容器上,可以减少不必要的事件传播,提高性能。
  2. 作用域:如果页面上有多个 React 版本(如微前端),事件都会被附加在 document 上,这时嵌套的 React 树调用 e.stopPropagation() 停止了事件冒泡, 外部的树仍会接收到该事件(因为只是阻止了 React 事件的冒泡), 这就使嵌套不同版本的 React 难以实现。
  3. 第三方库:与作用域同理。