Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
React 的事件委托、合成事件封装以及从根节点冒泡的机制。
React 不会将事件监听器直接附加到 DOM 元素上。相反,它使用事件委托 — 在根容器(调用 ReactDOM.createRoot() 的 div)附加单个监听器。当任何原生事件冒泡到根时,React 拦截它,将其包装在 SyntheticEvent 对象中,并通过 React 组件树分发。
当你在 JSX 中写 onClick='{'handleClick'}' 时,React 不会在该 DOM 节点上调用 addEventListener。相反,它在根容器上注册 click 事件的单个监听器 — 为整个应用只做一次,而不是每个组件一次。
当你点击一个按钮时,浏览器在按钮上触发原生 click 事件,该事件通过真实 DOM 树冒泡到文档根 — React 的监听器在那里等待。
React 将原生事件包装在 SyntheticEvent 中 — 一个跨浏览器规范化的事件对象,无论浏览器如何都暴露相同的 API。在旧版 React 中,SyntheticEvents 为了性能被池化和复用。
React 从目标元素向上遍历 Fiber 树直到根,在每个 Fiber 上调用找到的 onClick(或其他事件)处理器。这在 React 的虚拟树中模拟了浏览器冒泡。
就像原生事件一样,React 通过 onClickCapture 支持捕获阶段处理器。这些处理器自顶向下触发(根 → 目标),在冒泡阶段处理器自底向上触发(目标 → 根)之前。
根上的单个监听器处理该类型的所有事件。比在每个交互元素上附加监听器高效得多。
React 的跨浏览器事件包装器。规范化浏览器之间的不一致性。暴露 nativeEvent 属性用于原始访问。
事件从目标元素向上通过祖先元素传播。大多数 React 事件处理器的默认阶段。
事件在冒泡前从根向下传播到目标。使用 onClickCapture 进行捕获阶段处理。
停止事件在 React 模拟树中的进一步传播。除非在 e.nativeEvent 上调用,否则不会停止原生事件。
1// React attaches ONE listener to the root — not to each button:2// root.addEventListener('click', reactHandler)34function App() {5 return (6 <div onClick={() => console.log('div — bubble')}>7 <button8 onClick={(e) => {9 console.log('button — bubble');10 // e.stopPropagation() would stop div from firing11 }}12 onClickCapture={() => console.log('button — capture')}13 >14 Click me15 </button>16 </div>17 );18}1920// Click order:21// 1. button — capture (capture phase, top-down)22// 2. button — bubble (bubble phase, bottom-up)23// 3. div — bubble (bubble phase, continues up)
事件委托使 React 高度高效 — 无论应用中有多少按钮,每种事件类型只有一个原生事件监听器。这也意味着 React 可以拦截和处理动态添加组件的事件,并使 React 的合成事件系统在所有浏览器中一致工作。
You add a native `document.addEventListener('click', closeMenu)` to close a dropdown on outside click. Inside the dropdown, you call `e.stopPropagation()` on the React onClick — but the menu still closes.
React's stopPropagation stops propagation within React's synthetic event system (which uses delegation at the root). But the native listener on `document` has already received the event since React 17+ attaches to `root`, not `document`. The native handler fires before React can stop it.
Use `e.nativeEvent.stopImmediatePropagation()` to stop the native event from reaching other native listeners, or better yet, replace the native listener with a React `onClickCapture` on a wrapper div. Keep event handling within React's system to avoid mixed delegation conflicts.
Takeaway: React's event system runs inside a single delegated listener at the root container. Mixing React events with native `addEventListener` creates ordering conflicts. Prefer keeping all event logic within React's system for predictable behavior.
Legacy code from React 16 calls `e.persist()` in event handlers to use the event in async callbacks. After upgrading to React 18, you see that removing `e.persist()` works fine — but no one on the team understands why.
React 16 used event pooling — SyntheticEvent objects were reused after the handler returned, setting all properties to null. Accessing `e.target` in a setTimeout would return null unless you called `e.persist()`. Teams kept adding persist() defensively everywhere.
React 17+ removed event pooling entirely. SyntheticEvent objects are no longer reused — they persist naturally. All `e.persist()` calls can be safely removed. Understanding this change helps teams clean up legacy code and stop defensive coding patterns that are no longer needed.
Takeaway: React's event system evolves across versions. Event pooling removal (React 17), root-level delegation (React 17), and automatic batching in event handlers (React 18) are all changes that affect how you write event handling code. Stay current with the release notes.