菜单

DOM 事件系统

相关源文件

React 中的 DOM 事件系统负责规范化跨不同平台的浏览器事件,并处理事件委托以实现高效的事件处理。它充当原生 DOM 事件与 React 合成事件系统之间的桥梁,为 React 组件提供一致的跨浏览器事件接口。

概述

React 的 DOM 事件系统实现了一种事件委托形式,其中大多数事件都附加在文档级别,而不是直接附加在 DOM 节点上。这种方法提供了显著的性能优势,并简化了跨组件边界的事件处理。

来源

  • packages/react-dom/src/events/tests/DOMPluginEventSystem-test.internal.js

事件委托架构

React 不会将事件监听器附加到每个带有事件处理器的 DOM 节点上,而是将大多数事件监听器附加到文档级别(或根容器级别)。当事件发生时,React 会

  1. 捕获原生浏览器事件
  2. 确定哪些 React 组件应该接收事件
  3. 创建合成事件对象
  4. 通过 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 中的事件传播如下:

  1. 捕获阶段:事件从根部流向目标(自上而下)
  2. 目标阶段:事件到达目标元素
  3. 冒泡阶段:事件从目标流回根部(自下而上)

React 组件可以使用以下方式为任一阶段附加处理程序:

  • 常规事件属性(onClickonFocus)用于冒泡阶段
  • 捕获事件属性(onClickCaptureonFocusCapture)用于捕获阶段

来源

  • 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 中不冒泡(例如 onMouseEnteronMouseLeave)。React 对这些事件进行了特殊处理,以确保它们能与 React 的事件系统正确协同工作。

焦点事件

React 规范化了跨浏览器的焦点事件(onFocusonBlur),并在必要时使用 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)集成,以在服务器端渲染期间和之后正确处理事件

  1. 在水合过程中,React 优先水合接收用户交互的组件
  2. Suspense 边界上的事件会进行特殊管理,以确保正确行为
  3. 某些事件在水合完成后可能会被重新播放

这确保了服务器渲染的内容可以尽快变得可交互,并优先处理关键交互。

来源

  • 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