前言
在前端开发中,事件处理是一个非常重要的概念。React 作为最流行的前端框架之一,为了实现跨浏览器兼容性和更好的性能,设计了一套自己的事件系统------合成事件(Synthetic Event) 。
本文将深入解析 React 合成事件的工作原理,以及 React 17 中关于合成事件的重大改进,帮助你更好地理解 React 的事件机制。
什么是 React 合成事件?
React 合成事件是 React 封装的一套事件系统,它是对原生 DOM 事件的封装。当你在 JSX 中写 onClick
、onChange
等事件时,实际上绑定的是 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
时,整个事件处理流程如下:
阶段一:事件注册
- 在组件创建和更新时,React 会将事件(
listenTo
)委托注册到根节点; - 事件信息存储在
listenerBank
(事件池)中;
阶段二:事件触发
- 用户点击时,原生 DOM 事件首先触发;
- 原生事件通过事件冒泡机制,冒泡到根节点;
- 根节点接收到冒泡事件后,找到目标 DOM 元素;
- 通过 DOM 元素上的
__reactInternalInstance
属性,找到对应的 React 元素;
阶段三:事件处理
- React 从当前 React 元素开始,向上遍历所有父组件;
- 收集所有相关的事件回调函数,存储在
eventQueue
中; - 根据事件类型构建合成事件对象;
- 按顺序执行
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>
);
}
在上面的例子中,当用户点击按钮时,输出顺序会是:
- "按钮被点击";
- "外层 div 被点击" (被阻止,不会输出);
- "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 节点
这个修复的关键在于事件委托节点的层级:
-
React 17 之前的问题:
- 所有 React 应用的事件都委托到
document
; - 当调用
e.stopPropagation()
时,只能阻止合成事件在 React 内部的冒泡; - 但原生事件仍然会冒泡到
document
,因为document
是原生事件的监听者;
- 所有 React 应用的事件都委托到
-
React 17 之后的解决方案:
- 事件委托到应用的根节点(通常是
#root
或#app
); - 当调用
e.stopPropagation()
时,合成事件不会冒泡到根节点; - 由于根节点是 React 事件系统的边界,原生事件不会继续向上冒泡到
document
; - 这样就实现了真正的事件冒泡阻止。
- 事件委托到应用的根节点(通常是
总结
React 合成事件是 React 框架的重要组成部分,它通过事件委托机制提供了跨浏览器兼容性和更好的性能。React 17 的改进解决了事件冒泡控制的问题,使得事件处理更加可靠和可预测。
理解 React 合成事件的原理,不仅有助于我们更好地使用 React,还能帮助我们避免一些常见的事件处理陷阱。在实际开发中,我们应该充分利用 React 17 的改进,编写更加健壮的事件处理代码。