React 事件系统listenToAllSupportedEvents的实现

 更新时间:2026年07月13日 08:46:40   作者:YFF菲菲兔  
本文主要介绍了React 事件系统listenToAllSupportedEvents的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

React DOM 事件系统的初始化入口,在 createRoot 时调用,接收 React 根容器 DOM 作为入参,一次性给根容器挂载全部 React 支持的原生事件监听器,实现 React 事件委托(合成事件底层核心)

完整初始化流程

应用启动:
  createRoot(document.getElementById('root'))
    → listenToAllSupportedEvents(containerDiv)
      │
      ├─ containerDiv['_reactListening' + rand] = true
      │
      ├─ allNativeEvents = { click, keydown, mouseup, scroll, wheel, ... ~85 个 }
      │
      ├─ click (非 nonDelegatedEvents):
      │   ├─ listenToNativeEvent('click', false, containerDiv)
      │   │   → addTrappedEventListener(containerDiv, 'click', 0, false)
      │   │     → createEventListenerWrapperWithPriority(container, 'click', 0)
      │   │       → getEventPriority('click') → DiscreteEventPriority
      │   │       → wrapper = dispatchDiscreteEvent.bind(null, 'click', 0, container)
      │   │     → containerDiv.addEventListener('click', wrapper, false)   ← bubble
      │   └─ listenToNativeEvent('click', true, containerDiv)
      │       → addTrappedEventListener(containerDiv, 'click', IS_CAPTURE, true)
      │         → containerDiv.addEventListener('click', wrapper, true)    ← capture
      │
      ├─ scroll (nonDelegatedEvents):
      │   └─ listenToNativeEvent('scroll', true, containerDiv)  ← 只有 capture!
      │       → containerDiv.addEventListener('scroll', wrapper, true)
      │
      ├─ touchstart (nonDelegatedEvents 无, 但 passive):
      │   ├─ listenToNativeEvent('touchstart', false, containerDiv)
      │   │   → isPassiveListener = true
      │   │   → containerDiv.addEventListener('touchstart', wrapper, { passive: true })
      │   └─ listenToNativeEvent('touchstart', true, containerDiv)
      │       → container.addEventListener('touchstart', wrapper, { capture: true, passive: true })
      │
      └─ selectionchange:
          ├─ ownerDocument = document
          └─ document['_reactListening' + rand] = true
          └─ listenToNativeEvent('selectionchange', false, document)
            → document.addEventListener('selectionchange', wrapper, false)

绑定后 DOM 上的结果

<div id="root" (containerDiv)>
  ├─ click (bubble): listener
  ├─ click (capture): listener
  ├─ keydown (bubble): listener
  ├─ keydown (capture): listener
  ├─ scroll (capture): listener  ← 无 bubble
  ├─ wheel (bubble, passive): listener
  ├─ wheel (capture, passive): listener
  ├─ touchstart (bubble, passive): listener
  ├─ touchstart (capture, passive): listener
  └─ ... (~85 个事件,每个事件 1-2 个 listener)
      ...
document:
  └─ selectionchange (bubble): listener

源码解析

重复绑定拦截判断

const listeningMarker = '_reactListening' + Math.random().toString(36).slice(2);

if (!(rootContainerElement: any)[listeningMarker]) {
  (rootContainerElement: any)[listeningMarker] = true;
}

作用

判断根容器 DOM 上是否已经挂载过 listeningMarker 标记

  • 不存在标记 → 未绑定过事件,进入绑定逻辑,在根容器 DOM 上挂载唯一标记,标记当前容器已完成全部事件绑定,后续再次调用函数会直接跳过绑定逻辑
  • 已存在标记 → 直接跳过,避免重复执行绑定

设计意义

整个事件监听初始化是一次性的:所有事件的 listener 在 rootContainer 创建时注册一次,之后 fiber 的 mount/update/unmount 通过 __reactFiber$<hash> 和 __reactProps$<hash> 从 DOM 节点获取当前事件处理函数,不需要重新注册

遍历所有原生支持事件

allNativeEvents.forEach(domEventName => {
  if (domEventName !== 'selectionchange') {
    if (!nonDelegatedEvents.has(domEventName)) {
      // 委托事件 → rootContainer 上添加 bubble 监听
      listenToNativeEvent(domEventName, false, rootContainerElement);
    }
    // 全部事件 → rootContainer 上添加 capture 监听
    listenToNativeEvent(domEventName, true, rootContainerElement);
  }
});

作用

  • 循环遍历 React 内置全部原生事件列表,逐个处理每种事件的捕获 / 冒泡监听
  • 把 selectionchange 事件从通用循环逻辑中剥离,不走下面的容器绑定逻辑,留到函数末尾单独绑定到 document。因为 selectionchange 不冒泡,也不会在特定元素上触发,因为它在 Document 上触发,而不是在具体 DOM 元素上,无法通过根容器代理且仅能绑定在 document 节点,绑定在普通 DOM 容器不会触发
  • 非委托事件:对可冒泡委托事件,在根容器注册冒泡阶段监听器
  • 所有事件统一绑定捕获阶段监听:全部在根容器注册捕获阶段监听器

事件的监听策略

条件BubbleCapture示例
委托事件click: bubble + capture
非委托事件scroll: capture only
selectionchange❌(跳过)❌(跳过)单独处理

设计意义

  • React 19 将事件委托到 rootContainer。createRoot 时一次性绑定所有事件,之后 fiber 的增删改不需要操作原生 addEventListener,只需要在 fiber 上记录 updateQueue,事件触发时再通过 getListener 从 fiber 树中查找
  • allNativeEvents:React 内部维护的所有支持的原生 DOM 事件名全集(click、mouseover、input、change 等)
  • nonDelegatedEvents:Set 集合,存储不支持事件委托、必须直接绑定到目标元素的事件(如 scroll、focus、blur 等,这类事件不冒泡,无法通过根容器代理)
  • React 的合成事件模拟了捕获 + 冒泡两阶段:
    • capture 阶段:从根到目标(root → ... → target)
    • bubble 阶段:从目标到根(target → ... → root)
  • React 通过 capture listener 实现 capture 阶段模拟:
    • React 在 rootContainer 上注册 capture listener
    • 收到事件后,从 target 向上遍历 fiber 树构建 listener 列表
    • 按捕获顺序(逆序)执行
  • React 通过 bubble listener 实现 bubble 阶段模拟:
    • React 在 rootContainer 上注册 bubble listener
    • 收到事件后,从 target 向上遍历 fiber 树构建 listener 列表
    • 按冒泡顺序(正序)执行

处理 document 与 selectionchange

const ownerDocument =
  rootContainerElement.nodeType === DOCUMENT_NODE
    ? rootContainerElement
    : rootContainerElement.ownerDocument;

if (ownerDocument !== null) {
  if (!ownerDocument[listeningMarker]) {
    ownerDocument[listeningMarker] = true;
    listenToNativeEvent('selectionchange', false, ownerDocument);
  }
}

作用

  • 获取根容器所属的 document 对象
    • 如果根容器本身就是 document(SSR / 特殊挂载场景),直接赋值自身;
    • 普通 #root 容器,取其 ownerDocument 获取页面文档对象
  • document 判空保护:兼容 SSR、脱离文档流的 DOM 节点,防止 ownerDocument 为 null 时执行绑定报错,做空值安全防护
  • document 重复绑定拦截(同根容器标记逻辑):给 document 也挂载独立标记,防止重复绑定 selectionchange,多根实例复用同一个 document 监听
  • 绑定特殊事件 selectionchange:在 document 上绑定冒泡阶段的 selectionchange 监听器

设计意义

selectionchange 是 React 事件系统中唯一一个不在 rootContainer 上监听的事件。它作为特例在 listenToAllSupportedEvents 中单独处理,使用独立的 listeningMarker 防重,同一 document 的多个 root 共享同一个 document 监听

辅助函数

listenToNativeEvent

接收事件名、捕获 / 冒泡标识、绑定目标 DOM,组装事件标记位,调用底层真正注册监听的函数 addTrappedEventListener

export function listenToNativeEvent(
  domEventName,
  isCapturePhaseListener,
  target,
) {

  let eventSystemFlags = 0;
  // 标记当前监听处于捕获阶段
  if (isCapturePhaseListener) {
    eventSystemFlags |= IS_CAPTURE_PHASE;
  }
  addTrappedEventListener(
    target,
    domEventName,
    eventSystemFlags,
    isCapturePhaseListener,
  );
}

作用

  • 参数预处理,生成事件类型标记位:初始化事件系统标记位,如果是捕获监听,打上捕获标记,后续在事件触发回调里,通过判断 eventSystemFlags 区分当前是捕获监听还是冒泡监听,用来匹配 onClickCapture / onClick
  • 转发调用底层注册函数 addTrappedEventListener
    • 创建 React 内部封装的拦截回调(不是原生回调,里面包含合成事件构造、批量更新、优先级调度)
    • 执行原生 target.addEventListener(domEventName, 封装回调, isCapturePhaseListener)
    • 缓存监听器映射,用于后续卸载、事件清理

设计思想

  • 一次性委托:createRoot 时将所有事件监听器绑定到 rootContainer,此后 fiber 的 mount/update/unmount 不需要操作 addEventListener,通过 __reactProps$ 在 fiber 上获取当前 handler
  • capture + bubble 双监听:每个事件绑定两个监听器(非委托事件只绑 capture),React 通过从 target 到 root 的 fiber 树遍历 + listener 列表正序/逆序执行模拟完整的两阶段事件流
  • 被动事件优化:touchstart / touchmove / wheel 使用 { passive: true },浏览器承诺不调用 preventDefault(),允许合成层在事件处理完成前就开始滚动,消除 touchstart 导致的滚动延迟
  • 非委托事件单独处理:scroll, load, invalid, toggle 等不冒泡或性能敏感事件只在 capture 阶段监听,避免冒泡到 root 的空遍历
  • selectionchange 特殊路径:唯一不在 rootContainer 上监听的事件——它不在元素上触发,在 Document 上触发;使用独立的 listeningMarker 防重
  • 随机标记防冲突:_reactListening + 随机后缀避免与第三方库在 DOM 上的属性碰撞
  • 惰性提取 + 批量 dispatch:listenToAllSupportedEvents 只绑定 listener,不关注 handler。handler 通过 accumulateSinglePhaseListeners 在事件触发时按 fiber 树收集,绑定时不知道具体有哪些 onClick

到此这篇关于React 事件系统listenToAllSupportedEvents的实现的文章就介绍到这了,更多相关React listenToAllSupportedEvents内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • ReactNative之FlatList的具体使用方法

    ReactNative之FlatList的具体使用方法

    本篇文章主要介绍了ReactNative之FlatList的具体使用方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • React内存泄漏的常见原因及避免策略

    React内存泄漏的常见原因及避免策略

    内存泄漏是指程序中分配的内存未能正确释放,导致内存占用不断增加,最终可能影响应用性能甚至崩溃,在React中,内存泄漏常发生于组件卸载后,本文将详细介绍内存泄漏在React中的常见原因及避免策略,需要的朋友可以参考下
    2025-03-03
  • React Markdown配置示例

    React Markdown配置示例

    React-Markdown 是一个基于 React 的 Markdown 渲染组件库,其核心设计理念是通过 Unified 生态系统实现安全、可扩展的 Markdown 解析
    2025-04-04
  • React踩坑之antd输入框rules中的required=true问题

    React踩坑之antd输入框rules中的required=true问题

    这篇文章主要介绍了React踩坑之antd输入框rules中的required=true问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • react native reanimated实现动画示例详解

    react native reanimated实现动画示例详解

    这篇文章主要为大家介绍了react native reanimated实现动画示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • React Native 中实现倒计时功能

    React Native 中实现倒计时功能

    这篇文章主要介绍了React Native中实现倒计时功能示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • 基于React编写一个全局Toast的示例代码

    基于React编写一个全局Toast的示例代码

    前些日子在做项目的时候,需要封装一个Toast组件,我想起之前用过的库,只要在入口文件中引入就可以在全局中使用,还是很方便的,借这次机会也来实现一下,所以本文介绍了React中如何编写一个全局Toast,需要的朋友可以参考下
    2024-05-05
  • 简析React Native startReactApplication 方法

    简析React Native startReactApplication 方法

    这篇文章主要介绍了React Native startReactApplication 方法简析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • React.js中组件重渲染性能问题及优化过程

    React.js中组件重渲染性能问题及优化过程

    这篇文章主要介绍了React.js中组件重渲染性能问题及优化过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-05-05
  • React中super()和super(props)的区别小结

    React中super()和super(props)的区别小结

    本文主要介绍了React中super()和super(props)的区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-03-03

最新评论