React 合成事件原理:从事件委托到 React 17 的重大改进

前言

在前端开发中,事件处理是一个非常重要的概念。React 作为最流行的前端框架之一,为了实现跨浏览器兼容性和更好的性能,设计了一套自己的事件系统------合成事件(Synthetic Event)

本文将深入解析 React 合成事件的工作原理,以及 React 17 中关于合成事件的重大改进,帮助你更好地理解 React 的事件机制。

什么是 React 合成事件?

React 合成事件是 React 封装的一套事件系统,它是对原生 DOM 事件的封装。当你在 JSX 中写 onClickonChange 等事件时,实际上绑定的是 React 的合成事件,而不是原生的 DOM 事件。

tsx 复制代码
function MyComponent() {
  const handleClick = (e) => {
    console.log('这是一个 React 合成事件');
    console.log('事件对象:', e);
  };

  return (
    <button onClick={handleClick}>
      点击我
    </button>
  );
}

React 合成事件的核心原理

1. 事件委托机制

React 合成事件的核心是事件委托。React 不会为每个 DOM 节点都绑定事件监听器,而是将所有事件委托到一个统一的节点上。

  • React 17 之前 :事件委托到 document 节点;
  • React 17 之后:事件委托到应用的根节点(root);

2. 事件处理流程

话不多说,先上图:

让我们通过一个具体的例子来理解 React 合成事件的处理流程:

tsx 复制代码
class TaskEvent extends React.PureComponent {
  render() {
    return (
      <div
        onClick={() => {
          console.log('我是注册事件');
        }}
      >
        点击我
      </div>
    );
  }
}

当用户点击这个 div 时,整个事件处理流程如下:

阶段一:事件注册

  1. 在组件创建和更新时,React 会将事件(listenTo)委托注册到根节点;
  2. 事件信息存储在 listenerBank(事件池)中;

阶段二:事件触发

  1. 用户点击时,原生 DOM 事件首先触发;
  2. 原生事件通过事件冒泡机制,冒泡到根节点;
  3. 根节点接收到冒泡事件后,找到目标 DOM 元素;
  4. 通过 DOM 元素上的 __reactInternalInstance 属性,找到对应的 React 元素;

阶段三:事件处理

  1. React 从当前 React 元素开始,向上遍历所有父组件;
  2. 收集所有相关的事件回调函数,存储在 eventQueue 中;
  3. 根据事件类型构建合成事件对象;
  4. 按顺序执行 eventQueue 中的回调函数;

React 17 的重大改进

问题背景

在 React 17 之前,合成事件存在一个严重的问题:e.stopPropagation() 无法阻止原生事件的冒泡且不同版本的 React 应用共存会相互影响

tsx 复制代码
// React 17 之前的问题示例
function App() {
  useEffect(() => {
    // 在 document 上添加原生事件监听器
    const handleDocumentClick = () => {
      console.log('document 被点击了!');
    };
    
    document.addEventListener('click', handleDocumentClick);
    
    return () => {
      document.removeEventListener('click', handleDocumentClick);
    };
  }, []);

  return (
    <div onClick={() => console.log('外层 div 被点击')}>
      <button
        onClick={(e) => {
          e.stopPropagation(); // 这个只能阻止合成事件冒泡
          console.log('按钮被点击');
        }}
      >
        点击我
      </button>
    </div>
  );
}

在上面的例子中,当用户点击按钮时,输出顺序会是:

  1. "按钮被点击";
  2. "外层 div 被点击" (被阻止,不会输出);
  3. "document 被点击了!" (仍然会输出,因为原生事件冒泡到了 document);

React 17 的解决方案

React 17 通过改变事件委托的宿主来解决这个问题。

tsx 复制代码
// React 17 之前
const root = document.getElementById('root');
ReactDOM.render(<App />, root);
// 事件委托到 document

// React 17 之后
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
// 事件委托到 root 节点

这个修复的关键在于事件委托节点的层级

  1. React 17 之前的问题

    • 所有 React 应用的事件都委托到 document
    • 当调用 e.stopPropagation() 时,只能阻止合成事件在 React 内部的冒泡;
    • 但原生事件仍然会冒泡到 document,因为 document 是原生事件的监听者;
  2. React 17 之后的解决方案

    • 事件委托到应用的根节点(通常是 #root#app);
    • 当调用 e.stopPropagation() 时,合成事件不会冒泡到根节点;
    • 由于根节点是 React 事件系统的边界,原生事件不会继续向上冒泡到 document
    • 这样就实现了真正的事件冒泡阻止。

总结

React 合成事件是 React 框架的重要组成部分,它通过事件委托机制提供了跨浏览器兼容性和更好的性能。React 17 的改进解决了事件冒泡控制的问题,使得事件处理更加可靠和可预测。

理解 React 合成事件的原理,不仅有助于我们更好地使用 React,还能帮助我们避免一些常见的事件处理陷阱。在实际开发中,我们应该充分利用 React 17 的改进,编写更加健壮的事件处理代码。

参考资料

相关推荐
用户10922571561015 分钟前
你以为的 Tailwind 并不高效,看看这些使用误区
前端
意会29 分钟前
微信闪照小程序实现
前端·css·微信小程序
onejason29 分钟前
《利用 Python 爬虫获取 Amazon 商品详情实战指南》
前端·后端·python
用户67375280188433 分钟前
鸿蒙开发:应用内如何做更新
前端
zxhnext1 小时前
LLM大语言模型入门
前端·后端
知心宝贝1 小时前
写了那么久的前端,你真的了解浏览器背后的“小动作“吗?
前端·程序员·浏览器
wycode1 小时前
Vue2实践(2)之用component做一个动态表单(一)
前端·javascript·vue.js
维李设论1 小时前
前端智能化 | AG-UI实践及原理浅析
前端·aigc·agent
第七种黄昏1 小时前
Vue3 中的 ref、模板引用和 defineExpose 详解
前端·javascript·vue.js
一只卡比兽1 小时前
动态规划与贪心算法详解:原理、对比与代码实践
前端