从v17.0.0开始, React 不会再将事件处理添加到 document 上, 而是将事件处理添加到渲染 React 树的根 DOM 容器中.
引入官方提供的图片:

图中清晰的展示了v17.0.0的改动, 无论是在document还是根 DOM 容器上监听事件, 都可以归为事件委托(代理)(mdn).
注意: react的事件体系, 不是全部都通过事件委托来实现的. 有一些特殊情况, 是直接绑定到对应 DOM 元素上的(如:scroll, load), 它们都通过listenToNonDelegatedEvent函数进行绑定.
上述特殊事件最大的不同是监听的 DOM 元素不同, 除此之外, 其他地方的实现与正常事件大体一致.
本节讨论的是可以被根 DOM 容器代理的正常事件.
在前文React 应用的启动过程中介绍了React在启动时会创建全局对象, 其中在创建fiberRoot对象时, 调用createRootImpl:
- function createRootImpl(
- container: Container,
- tag: RootTag,
- options: void | RootOptions,
- ) {
-
- // ... 省略无关代码
-
- if (enableEagerRootListeners) {
-
- const rootContainerElement =
-
- container.nodeType === COMMENT_NODE ? container.parentNode : container;
-
- listenToAllSupportedEvents(rootContainerElement);
-
- }
-
- // ... 省略无关代码
-
- }
listenToAllSupportedEvents函数, 实际上完成了事件代理:
- // ... 省略无关代码
-
- export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
-
- if (enableEagerRootListeners) {
-
- // 1. 节流优化, 保证全局注册只被调用一次
-
- if ((rootContainerElement: any)[listeningMarker]) {
-
- return;
-
- }
-
- (rootContainerElement: any)[listeningMarker] = true;
-
- // 2. 遍历allNativeEvents 监听冒泡和捕获阶段的事件
-
- allNativeEvents.forEach(domEventName => {
-
- if (!nonDelegatedEvents.has(domEventName)) {
-
- listenToNativeEvent(
-
- domEventName,
-
- false, // 冒泡阶段监听
-
- ((rootContainerElement: any): Element),
-
- null,
-
- );
-
- }
-
- listenToNativeEvent(
-
- domEventName,
-
- true, // 捕获阶段监听
-
- ((rootContainerElement: any): Element),
-
- null,
-
- );
-
- });
-
- }
-
- }
核心逻辑:
allNativeEvents, 调用listenToNativeEvent监听冒泡和捕获阶段的事件.
allNativeEvents包括了大量的原生事件名称, 它是在DOMPluginEventSystem.js中被初始化- // ... 省略无关代码
-
- export function listenToNativeEvent(
- domEventName: DOMEventName,
- isCapturePhaseListener: boolean,
- rootContainerElement: EventTarget,
- targetElement: Element | null,
- eventSystemFlags?: EventSystemFlags = 0,
- ): void {
-
- let target = rootContainerElement;
-
- const listenerSet = getEventListenerSet(target);
-
- const listenerSetKey = getListenerSetKey(
-
- domEventName,
-
- isCapturePhaseListener,
-
- );
-
- // 利用set数据结构, 保证相同的事件类型只会被注册一次.
-
- if (!listenerSet.has(listenerSetKey)) {
-
- if (isCapturePhaseListener) {
-
- eventSystemFlags |= IS_CAPTURE_PHASE;
-
- }
-
- // 注册事件监听
-
- addTrappedEventListener(
-
- target,
-
- domEventName,
-
- eventSystemFlags,
-
- isCapturePhaseListener,
-
- );
-
- listenerSet.add(listenerSetKey);
-
- }
-
- }
- // ... 省略无关代码
-
- function addTrappedEventListener(
- targetContainer: EventTarget,
- domEventName: DOMEventName,
- eventSystemFlags: EventSystemFlags,
- isCapturePhaseListener: boolean,
- isDeferredListenerForLegacyFBSupport?: boolean,
- ) {
-
- // 1. 构造listener
-
- let listener = createEventListenerWrapperWithPriority(
-
- targetContainer,
-
- domEventName,
-
- eventSystemFlags,
-
- );
-
- let unsubscribeListener;
-
- // 2. 注册事件监听
-
- if (isCapturePhaseListener) {
-
- unsubscribeListener = addEventCaptureListener(
-
- targetContainer,
-
- domEventName,
-
- listener,
-
- );
-
- } else {
-
- unsubscribeListener = addEventBubbleListener(
-
- targetContainer,
-
- domEventName,
-
- listener,
-
- );
-
- }
-
- }
-
- // 注册原生事件 冒泡
-
- export function addEventBubbleListener(
- target: EventTarget,
- eventType: string,
- listener: Function,
- ): Function {
-
- target.addEventListener(eventType, listener, false);
-
- return listener;
-
- }
-
- // 注册原生事件 捕获
-
- export function addEventCaptureListener(
- target: EventTarget,
- eventType: string,
- listener: Function,
- ): Function {
-
- target.addEventListener(eventType, listener, true);
-
- return listener;
-
- }
从listenToAllSupportedEvents开始, 调用链路比较长, 最后调用addEventBubbleListener和addEventCaptureListener监听了原生事件.
在注册原生事件的过程中, 需要重点关注一下监听函数, 即listener函数. 它实现了把原生事件派发到react体系之内, 非常关键.
比如点击 DOM 触发原生事件, 原生事件最后会被派发到
react内部的onClick函数.listener函数就是这个由外至内的关键环节.
listener是通过createEventListenerWrapperWithPriority函数产生:
- export function createEventListenerWrapperWithPriority(
- targetContainer: EventTarget,
- domEventName: DOMEventName,
- eventSystemFlags: EventSystemFlags,
- ): Function {
-
- // 1. 根据优先级设置 listenerWrapper
-
- const eventPriority = getEventPriorityForPluginSystem(domEventName);
-
- let listenerWrapper;
-
- switch (eventPriority) {
-
- case DiscreteEvent:
-
- listenerWrapper = dispatchDiscreteEvent;
-
- break;
-
- case UserBlockingEvent:
-
- listenerWrapper = dispatchUserBlockingUpdate;
-
- break;
-
- case ContinuousEvent:
-
- default:
-
- listenerWrapper = dispatchEvent;
-
- break;
-
- }
-
- // 2. 返回 listenerWrapper
-
- return listenerWrapper.bind(
-
- null,
-
- domEventName,
-
- eventSystemFlags,
-
- targetContainer,
-
- );
-
- }
可以看到, 不同的domEventName调用getEventPriorityForPluginSystem后返回不同的优先级, 最终会有 3 种情况:
DiscreteEvent: 优先级最高, 包括click, keyDown, input等事件, 源码
listener是dispatchDiscreteEventUserBlockingEvent: 优先级适中, 包括drag, scroll等事件, 源码
listener是dispatchUserBlockingUpdateContinuousEvent: 优先级最低,包括animation, load等事件, 源码
listener是dispatchEvent这 3 种listener实际上都是对dispatchEvent的包装:
- // ...省略无关代码
-
- export function dispatchEvent(
- domEventName: DOMEventName,
- eventSystemFlags: EventSystemFlags,
- targetContainer: EventTarget,
- nativeEvent: AnyNativeEvent,
- ): void {
-
- if (!_enabled) {
-
- return;
-
- }
-
- const blockedOn = attemptToDispatchEvent(
-
- domEventName,
-
- eventSystemFlags,
-
- targetContainer,
-
- nativeEvent,
-
- );
-
- }
当原生事件触发之后, 首先会进入到dispatchEvent这个回调函数. 而dispatchEvent函数是react事件体系中最关键的函数, 其调用链路较长, 核心步骤如图所示:

重点关注其中 3 个核心环节:
attemptToDispatchEventSimpleEventPlugin.extractEventsprocessDispatchQueueattemptToDispatchEvent把原生事件和fiber树关联起来.
- export function attemptToDispatchEvent(
- domEventName: DOMEventName,
- eventSystemFlags: EventSystemFlags,
- targetContainer: EventTarget,
- nativeEvent: AnyNativeEvent,
- ): null | Container | SuspenseInstance {
-
- // ...省略无关代码
-
- // 1. 定位原生DOM节点
-
- const nativeEventTarget = getEventTarget(nativeEvent);
-
- // 2. 获取与DOM节点对应的fiber节点
-
- let targetInst = getClosestInstanceFromNode(nativeEventTarget);
-
- // 3. 通过插件系统, 派发事件
-
- dispatchEventForPluginEventSystem(
-
- domEventName,
-
- eventSystemFlags,
-
- nativeEvent,
-
- targetInst,
-
- targetContainer,
-
- );
-
- return null;
-
- }
核心逻辑:
getEventTargetgetClosestInstanceFromNodedispatchEventForPluginEventSystemdispatchEvent函数的调用链路中, 通过不同的插件, 处理不同的事件. 其中最常见的事件都会由SimpleEventPlugin.extractEvents进行处理:
- function extractEvents(
- dispatchQueue: DispatchQueue,
- domEventName: DOMEventName,
- targetInst: null | Fiber,
- nativeEvent: AnyNativeEvent,
- nativeEventTarget: null | EventTarget,
- eventSystemFlags: EventSystemFlags,
- targetContainer: EventTarget,
- ): void {
-
- const reactName = topLevelEventsToReactNames.get(domEventName);
-
- if (reactName === undefined) {
-
- return;
-
- }
-
- let SyntheticEventCtor = SyntheticEvent;
-
- let reactEventType: string = domEventName;
-
- const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
-
- const accumulateTargetOnly = !inCapturePhase && domEventName === 'scroll';
-
- // 1. 收集所有监听该事件的函数.
-
- const listeners = accumulateSinglePhaseListeners(
-
- targetInst,
-
- reactName,
-
- nativeEvent.type,
-
- inCapturePhase,
-
- accumulateTargetOnly,
-
- );
-
- if (listeners.length > 0) {
-
- // 2. 构造合成事件, 添加到派发队列
-
- const event = new SyntheticEventCtor(
-
- reactName,
-
- reactEventType,
-
- null,
-
- nativeEvent,
-
- nativeEventTarget,
-
- );
-
- dispatchQueue.push({ event, listeners });
-
- }
-
- }
核心逻辑:
收集所有listener回调
这里的是fiber.memoizedProps.onClick/onClickCapture等绑定在fiber节点上的回调函数
具体逻辑在accumulateSinglePhaseListeners:
- export function accumulateSinglePhaseListeners(
- targetFiber: Fiber | null,
- reactName: string | null,
- nativeEventType: string,
- inCapturePhase: boolean,
- accumulateTargetOnly: boolean,
- ): Array<DispatchListener> {
-
- const captureName = reactName !== null ? reactName + 'Capture' : null;
-
- const reactEventName = inCapturePhase ? captureName : reactName;
-
- const listeners: Array<DispatchListener> = [];
-
- let instance = targetFiber;
-
- let lastHostComponent = null;
-
- // 从targetFiber开始, 向上遍历, 直到 root 为止
-
- while (instance !== null) {
-
- const { stateNode, tag } = instance;
-
- // 当节点类型是HostComponent时(如: div, span, button等类型)
-
- if (tag === HostComponent && stateNode !== null) {
-
- lastHostComponent = stateNode;
-
- if (reactEventName !== null) {
-
- // 获取标准的监听函数 (如onClick , onClickCapture等)
-
- const listener = getListener(instance, reactEventName);
-
- if (listener != null) {
-
- listeners.push(
-
- createDispatchListener(instance, listener, lastHostComponent),
-
- );
-
- }
-
- }
-
- }
-
- // 如果只收集目标节点, 则不用向上遍历, 直接退出
-
- if (accumulateTargetOnly) {
-
- break;
-
- }
-
- instance = instance.return;
-
- }
-
- return listeners;
-
- }
构造合成事件(SyntheticEvent), 添加到派发队列(dispatchQueue)
SyntheticEvent, 是react内部创建的一个对象, 是原生事件的跨浏览器包装器, 拥有和浏览器原生事件相同的接口(stopPropagation,preventDefault), 抹平不同浏览器 api 的差异, 兼容性好.
具体的构造过程并不复杂, 可以直接查看源码.
此处我们需要知道, 在Plugin.extractEvents过程中, 遍历fiber树找到listener之后, 就会创建SyntheticEvent, 加入到dispatchQueue中, 等待派发.
extractEvents完成之后, 逻辑来到processDispatchQueue, 终于要真正执行派发了.
- export function processDispatchQueue(
- dispatchQueue: DispatchQueue,
- eventSystemFlags: EventSystemFlags,
- ): void {
-
- const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
-
- for (let i = 0; i < dispatchQueue.length; i++) {
-
- const { event, listeners } = dispatchQueue[i];
-
- processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
-
- }
-
- // ...省略无关代码
-
- }
-
- function processDispatchQueueItemsInOrder(
- event: ReactSyntheticEvent,
- dispatchListeners: Array
, - inCapturePhase: boolean,
- ): void {
-
- let previousInstance;
-
- if (inCapturePhase) {
-
- // 1. capture事件: 倒序遍历listeners
-
- for (let i = dispatchListeners.length - 1; i >= 0; i--) {
-
- const { instance, currentTarget, listener } = dispatchListeners[i];
-
- if (instance !== previousInstance && event.isPropagationStopped()) {
-
- return;
-
- }
-
- executeDispatch(event, listener, currentTarget);
-
- previousInstance = instance;
-
- }
-
- } else {
-
- // 2. bubble事件: 顺序遍历listeners
-
- for (let i = 0; i < dispatchListeners.length; i++) {
-
- const { instance, currentTarget, listener } = dispatchListeners[i];
-
- if (instance !== previousInstance && event.isPropagationStopped()) {
-
- return;
-
- }
-
- executeDispatch(event, listener, currentTarget);
-
- previousInstance = instance;
-
- }
-
- }
-
- }
在processDispatchQueueItemsInOrder遍历dispatchListeners数组, 执行executeDispatch派发事件, 在fiber节点上绑定的listener函数被执行.
在processDispatchQueueItemsInOrder函数中, 根据捕获(capture)或冒泡(bubble)的不同, 采取了不同的遍历方式:
capture事件: 从上至下调用fiber树中绑定的回调函数, 所以倒序遍历dispatchListeners.bubble事件: 从下至上调用fiber树中绑定的回调函数, 所以顺序遍历dispatchListeners.从架构上来讲, SyntheticEvent打通了从外部原生事件到内部fiber树的交互渠道, 使得react能够感知到浏览器提供的原生事件, 进而做出不同的响应, 修改fiber树, 变更视图等.
从实现上讲, 主要分为 3 步:
DOM元素和fiber元素listeners: 遍历fiber树, 收集所有监听本事件的listener函数.listeners进行派发.