🔥React的事件处理机制:合成事件/废弃的事件池🔥

121 阅读5分钟

React的事件处理机制

React 的事件处理机制是为了优化浏览器的原生事件系统,使之更适合在 React 应用中使用。主要特性包括合成事件(SyntheticEvent)和事件池等。

合成事件(SyntheticEvent)

合成事件(SyntheticEvent)是 React 为了解决跨浏览器兼容性和性能问题而引入的一层封装。它提供了一个统一的事件接口,使得开发者可以编写一致的事件处理代码,而不必担心不同浏览器之间的差异。

合成事件的特点

合成事件有三个特点:

  • 合成事件确保了不同浏览器之间事件行为的一致性。你不需要为不同的浏览器编写不同的事件处理逻辑。
  • React 使用事件委托来提高性能。事件处理器并不是直接绑定到每个 DOM 节点上,而是绑定到根节点上,通过事件冒泡机制来触发具体的处理器。
  • 为了减少内存消耗,React 重用事件对象。这意味着在事件处理函数执行完毕后,事件对象会被复用。因此,如果需要在异步回调中使用事件对象的数据,需要先将其属性保存在一个变量中。

第一点我相信大家都能理解,也就是React对各个浏览器进行了兼容,保证了我们用React写出了同样的代码,就有同样的功效,不像某些浏览器写了一样的代码却功能有差别。

至于第二点和第三点,我们要详细解释一下了。

React的事件委托

React 使用事件委托来提高性能。事件处理器并不是直接绑定到每个 DOM 节点上,而是绑定到根节点上,通过事件冒泡机制来触发具体的处理器。

为什么会使用事件委托

  • 减少事件处理器的数量:如果一个列表中有大量的项目,直接为每个项目绑定事件处理器会导致大量的事件处理器,这会消耗大量的内存和 CPU 资源。
  • 动态添加和删除元素:对于动态生成的内容,直接绑定事件处理器需要在每次内容变化时重新绑定,而事件委托可以避免这种情况。

在 React 中,事件委托是自动实现的。React 不会为每个单独的 DOM 元素绑定事件处理器,而是将事件处理器绑定到文档根节点(通常是 document)。当事件触发时,React 会通过事件冒泡机制捕获事件,并根据事件的目标元素来调用相应的事件处理器。

整一个事件触发的流程是这样的:

flowchart LR
    A[原生DOM事件流] -->|捕获阶段| B[浏览器内核]
    B -->|冒泡到根节点| C[React根监听器]
    C --> D{React事件系统}
    D -->|1. 创建合成事件| E[SyntheticEvent]
    D -->|2. 重建组件路径| F[虚拟DOM树]
    D -->|3. 自定义传播| G[按需执行组件回调]

其中,合成事件可以理解为普通事件的加强版本,拥有一些普通事件没有的功能,之后重建路径,按照这个路径给虚拟dom又来了一遍捕获冒泡,其中在目标阶段时,触发onClick后的回调函数。

事件池(Event Pooling)

React 的 事件池(Event Pooling) 是一种性能优化策略,它通过 复用合成事件(SyntheticEvent)对象 减少内存分配和垃圾回收的负担。

  • React 的 SyntheticEvent(合成事件)在触发时会被 临时创建 并放入一个 共享的事件池
  • 当事件回调执行完毕后,React 会 重置事件对象 并放回池中,供后续事件复用。
  • 目的:避免频繁创建/销毁事件对象,提高性能(尤其在大量事件触发时)。

事件池中的事件对象大概长这个样子:

属性说明示例
event.target触发事件的 DOM 元素(如 <button>)。event.target.value(输入框的值)
event.currentTarget当前处理事件的 React 元素(可能不同于 target,由于事件委托)。event.currentTarget.getAttribute('id')
event.type事件类型(如 "click""change")。event.type === "click"
event.nativeEvent原始浏览器事件(MouseEventKeyboardEvent)。event.nativeEvent.clientX(鼠标位置)
event.preventDefault()阻止默认行为(如表单提交、链接跳转)。event.preventDefault()
event.stopPropagation()阻止事件冒泡(仅影响 React 合成事件)。event.stopPropagation()

当有事件触发的时候,就会从池中拿出一个对象,给它赋值,用完了以后就清空里面的值再放池中,准备复用。

所以在老版本中,这么做访问不到对象:

function handleClick(event) {
  console.log(event.target); // 正常访问(同步)
  
  setTimeout(() => {
    console.log(event.target); // null(事件已被回收)
  }, 100);
}

<button onClick={handleClick}>点击</button>

因为在执行完handleClick()后,event对象已经被回收了,里面的值全部清空了。

但是在React17之后的版本,它移除了事件池的功能,合成事件对象 不再被池化,每次事件触发都会创建一个新对象。

虽然它已经被废止了,但是它的设计思想还是很有借鉴意义的,当我们空间不足的时候,可以借鉴这种思想。

总结

我们今天深入探讨了React的事件处理机制,特别是合成事件(SyntheticEvent)和事件委托(Event Delegation)。合成事件确保了跨浏览器的一致性,并通过事件委托提高了性能。事件委托减少了事件处理器的数量,优化了动态内容的处理。虽然事件池在React 17之前通过复用事件对象减少内存消耗,但React 17及之后版本移除了这一功能,每次事件触发都会创建新的事件对象,简化了异步访问并提升了安全性。这些机制共同提升了React应用的性能和用户体验。