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 的改进,编写更加健壮的事件处理代码。

参考资料

相关推荐
chéng ௹4 分钟前
uniapp 封装uni.showToast提示
前端·javascript·uni-app
tuokuac33 分钟前
nginx配置前端请求转发到指定的后端ip
前端·tcp/ip·nginx
程序员爱钓鱼36 分钟前
Go语言实战案例-开发一个Markdown转HTML工具
前端·后端·go
万少1 小时前
鸿蒙创新赛 HarmonyOS 6.0.0(20) 关键特性汇总
前端
还有多远.1 小时前
jsBridge接入流程
前端·javascript·vue.js·react.js
蝶恋舞者1 小时前
web 网页数据传输处理过程
前端
非凡ghost1 小时前
FxSound:提升音频体验,让音乐更动听
前端·学习·音视频·生活·软件需求
吃饭最爱2 小时前
html的基础知识
前端·html
我没想到原来他们都是一堆坏人2 小时前
(未完待续...)如何编写一个用于构建python web项目镜像的dockerfile文件
java·前端·python
前端Hardy2 小时前
HTML&CSS:有趣的漂流瓶
前端·javascript·css