React 事件系统listenToAllSupportedEvents的实现
作者: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 容器不会触发
- 非委托事件:对可冒泡委托事件,在根容器注册冒泡阶段监听器
- 所有事件统一绑定捕获阶段监听:全部在根容器注册捕获阶段监听器
事件的监听策略
| 条件 | Bubble | Capture | 示例 |
|---|---|---|---|
| 委托事件 | ✅ | ✅ | 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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
