React 独特的事件系统
首先要明确的是,React为了兼容全部的浏览器,模拟实现了一套自己的事件系统,也就是我们现在看到的事件都是被React处理过后的,给元素的事件也并不是真正的事件处理函数,所以原生事件中像 return false 阻止默认行为这种方式在 React 中是不生效的。
在 v17 之前,React事件绑定在 document 上,之后绑定在应用对应容器 container 上,可以理解为事件委托,当然也不是所有事件都会进行事件委托,例如 scroll 等还是直接绑定在DOM上的。并且 React 将事件源 event 进行了重写,所以我们拿到的不是真正的事件源。
事件合成
上面提到我们写的事件都绑定在了 document 或 container 上,并且一个事件可能由多个原生事件组成,
registrationNameDependencies 对象保存了每个事件是由哪写原生事件组成的,例如我们绑定一个 onChange,React 会绑定 blur change click … 方法
1 2 3 4 5 6 7
| { onBlur: ['blur'], onClick: ['click'], onClickCapture: ['click'], onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'], }
|
事件插件
registrationNameModules 对象保存了每个事件和处理该事件插件的映射,当触发某个事件就会通过这个对象找到对应的插件
1 2 3 4 5 6
| const registrationNameModules = { onBlur: SimpleEventPlugin, onClick: SimpleEventPlugin, onClickCapture: SimpleEventPlugin, }
|
事件绑定
老版本事件系统:事件监听(addEventListener) -> 捕获阶段执行 -> 冒泡阶段执行
新版本事件系统:捕获阶段执行 -> 事件监听 -> 冒泡阶段执行
老版本的事件系统模拟的冒泡和捕获阶段,其实都是在浏览器的冒泡阶段执行的,新版本的事件系统在创建 fiberRoot 时通过 listenToAllSupportedEvents 方法将全部事件注册完成事件代理,分别绑定了捕获和冒泡事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) { if (enableEagerRootListeners) { if ((rootContainerElement: any)[listeningMarker]) { return; } (rootContainerElement: any)[listeningMarker] = true; allNativeEvents.forEach((domEventName) => { if (!nonDelegatedEvents.has(domEventName)) { listenToNativeEvent(domEventName, false, ((rootContainerElement: any): Element), null,); } listenToNativeEvent(domEventName, true, ((rootContainerElement: any): Element), null,); }); } }
|
这里的 listenToNativeEvent 方法调用到最后就是通过 addEventCaptureListener 和 addEventBubbleListener 来注册冒泡/捕获的事件,本质都是原生的 addEventListener。
另外我们定义的事件函数最后都会保存在都会保存在 DOM 对应 fiber 的 memoizedProps 上

事件触发
绑定事件后,只要触发就会首先执行 React 的统一的事件处理函数 dispatchEvent,该方法调用会依次执行 dispatchEventForPluginEventSystem -> batchedUpdates -> dispatchEventsForPlugins
dispatchEventsForPlugins 触发
1 2 3 4 5 6 7 8 9 10 11
| function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) { var nativeEventTarget = getEventTarget(nativeEvent); var dispatchQueue = []; extractEvents(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags); processDispatchQueue(dispatchQueue, eventSystemFlags); }
|
其中 extractEvents 会根据不同的事件使用不同的插件生成对应的事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| 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'; const listeners = accumulateSinglePhaseListeners( targetInst, reactName, nativeEvent.type, inCapturePhase, accumulateTargetOnly, ); if (listeners.length > 0) { const event = new SyntheticEventCtor( reactName, reactEventType, null, nativeEvent, nativeEventTarget, ); dispatchQueue.push({event, listeners}); } }
|
accumulateSinglePhaseListeners
accumulateSinglePhaseListeners 找到 fiber 的 props 对应的事件加入到监听集合,然后进行递归知直到根节点,如果事件不冒泡就停止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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;
while (instance !== null) { const { stateNode, tag } = instance; if (tag === HostComponent && stateNode !== null) { lastHostComponent = stateNode; if (reactEventName !== null) { const listener = getListener(instance, reactEventName); if (listener != null) { listeners.push( createDispatchListener(instance, listener, lastHostComponent), ); } } } if (accumulateTargetOnly) { break; } instance = instance.return; } return listeners; }
|
processDispatchQueue 执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| 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<DispatchListener>, inCapturePhase: boolean, ): void { let previousInstance; if (inCapturePhase) { 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 { 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 如果是捕获事件,倒序执行,冒泡事件正序执行。
执行顺序
v16: document 捕获 => 原生捕获 => 原生冒泡 => 合成事件(react)捕获 => 合成事件(react)冒泡 => document 元素冒泡
v17/v18: document 捕获 => 合成事件(react)捕获 => 原生捕获 => 原生冒泡 => 合成事件(react)冒泡 => document 元素冒泡