DOM 事件系统
相关源文件
React 中的 DOM 事件系统负责规范化跨不同平台的浏览器事件,并处理事件委托以实现高效的事件处理。它充当原生 DOM 事件与 React 合成事件系统之间的桥梁,为 React 组件提供一致的跨浏览器事件接口。
概述
React 的 DOM 事件系统实现了一种事件委托形式,其中大多数事件都附加在文档级别,而不是直接附加在 DOM 节点上。这种方法提供了显著的性能优势,并简化了跨组件边界的事件处理。
来源
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js
事件委托架构
React 不会将事件监听器附加到每个带有事件处理器的 DOM 节点上,而是将大多数事件监听器附加到文档级别(或根容器级别)。当事件发生时,React 会
捕获原生浏览器事件
确定哪些 React 组件应该接收事件
创建合成事件对象
通过 React 的组件层级分派事件
这种架构允许 React
在一个地方高效管理事件
在不同浏览器中应用一致的行为
实现事件池(React 17 之前)和事件批处理等功能
来源
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:100-122
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:124-170
合成事件
React 将原生浏览器事件包装在一个名为 SyntheticEvent 的跨浏览器包装器中。这些事件具有与原生浏览器事件相同的接口,但在所有浏览器中都表现一致,并包含额外功能。
React 合成事件的主要特点
实现与原生事件相同的接口(stopPropagation()、preventDefault() 等)
在旧版 React(React 17 之前)中会进行事件池化
包含跨浏览器规范化的属性
支持捕获和冒泡阶段的事件处理
与 W3C 事件规范紧密对应
从 React 17 开始,React 不再为了性能而池化事件对象,从而简化了事件处理。
// Example of synthetic event usage in React component
<button
onClick={(e) => {
// 'e' is a SyntheticEvent, not a native event
e.preventDefault();
console.log('Button clicked');
}}
>
Click me
</button>
来源
packages/react-dom/src/tests /ReactDOMServerSelectiveHydration-test.internal.js:30-79
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:100-122
事件传播
React 的事件系统完全支持 DOM 事件模型中的标准捕获和冒泡阶段。React 中的事件传播如下:
捕获阶段 :事件从根部流向目标(自上而下)
目标阶段 :事件到达目标元素
冒泡阶段 :事件从目标流回根部(自下而上)
React 组件可以使用以下方式为任一阶段附加处理程序:
常规事件属性(onClick、onFocus)用于冒泡阶段
捕获事件属性(onClickCapture、onFocusCapture)用于捕获阶段
来源
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:124-170
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:728-879
跨组件边界的事件处理
React 的事件系统处理跨组件边界的事件,包括门户(portals)和多个 React 根。这使得无论 DOM 结构如何,事件传播都保持一致。
门户事件传播
在门户中触发的事件会通过 React 的组件层级冒泡,而不是通过 DOM 层级
来源
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:557-612
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:615-653
不连续节点处理
即使组件位于不连续的 DOM 树中,React 也能正确处理事件
来源
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:266-323
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:325-421
特殊事件类型
React 的事件系统以不同的方式处理不同类型的事件
冒泡事件
大多数 DOM 事件会自然地通过 DOM 树冒泡。React 利用这种行为来实现其事件委托系统(如点击、焦点等)。
非冒泡事件
有些事件在 DOM 中不冒泡(例如 onMouseEnter、onMouseLeave)。React 对这些事件进行了特殊处理,以确保它们能与 React 的事件系统正确协同工作。
焦点事件
React 规范化了跨浏览器的焦点事件(onFocus、onBlur),并在必要时使用 focusin/focusout 实现 polyfill,以确保一致的冒泡行为。
来源
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:896-942
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:1128-1268
与事件优先级的集成
React 将不同事件类型与不同优先级关联起来
优先级 事件类型 描述 离散型 点击、按键、焦点 应同步处理的用户交互 连续型 鼠标移动、滚动 可以以较低优先级处理的连续事件 默认 其他事件 以默认优先级处理
这种优先级划分有助于 React 优化其渲染,优先处理最重要的用户交互。
来源
packages/react-dom/src/tests /ReactDOMServerSelectiveHydration-test.internal.js:103-128
packages/react-dom/src/tests /ReactDOMServerSelectiveHydration-test.internal.js:636-726
事件插件系统
在 React 的原生渲染器中,事件是通过插件系统处理的。尽管现代 React DOM 已经摆脱了这种架构,但了解它有助于理解事件系统的设计。
来源
packages/react-native-renderer/src/legacy-events/EventPluginRegistry.js
packages/react-native-renderer/src/legacy-events/PluginModuleType.js
自定义事件处理 API
React 提供了一个不稳定的 API,用于创建自定义事件处理程序
该 API 允许在标准 React 属性模式之外进行更灵活的事件处理。
来源
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:1349-1412
packages/react-dom/src/tests /ReactDOMServerSelectiveHydration-test.internal.js:389-453
水合与事件
React 的事件系统与水合(hydration)集成,以在服务器端渲染期间和之后正确处理事件
在水合过程中,React 优先水合接收用户交互的组件
Suspense 边界上的事件会进行特殊管理,以确保正确行为
某些事件在水合完成后可能会被重新播放
这确保了服务器渲染的内容可以尽快变得可交互,并优先处理关键交互。
来源
packages/react-dom/src/tests /ReactDOMServerSelectiveHydration-test.internal.js:155-214
packages/react-dom/src/tests /ReactDOMServerSelectiveHydration-test.internal.js:1130-1201
React 的事件系统旨在提升性能
事件委托减少了事件监听器的数量
事件以适当的优先级级别进行处理
水合优先处理具有用户交互的组件
事件处理程序仅在需要时执行
这些优化有助于确保 React 应用即使在复杂的事件处理需求下也能保持响应。
来源
packages/react-dom/src/tests /ReactDOMServerSelectiveHydration-test.internal.js:1336-1442
packages/react-dom/src/events/tests /DOMPluginEventSystem-test.internal.js:100-122