React合成事件原理
date
Jan 28, 2022
slug
react-hechengshijian
status
Published
tags
React
源码
summary
从源码解析React合成事件原理
type
Post
React的事件系统采用了事件委托的思想,除了媒体类的事件无法被事务系统处理外,大部分事件都不会被绑定在具体元素上,而是统一绑定在document上。事件在dom节点上触发,冒泡到document上,document上的统一事件处理程序会将事件分发到具体的组件实例。分发前,react会对事件进行包装。
触发顺序
原生 -> 合成 -> document
本文基于React17.0.0
在React中,事件通过委托挂载到
root
(根DOM容器)上。但并不是所有的事件都是这样处理的,一些特殊情况会通过listenToNonDelegatedEvents
直接绑定到触发DOM元素上,如scroll
、load
、selectionchange
等。这些事件除了监听的DOM不同,别的基本没区别。React的合成事件,将原生事件与fiber链接了起来,从实现上分为:
- 监听原生事件: 将DOM元素和
fiber
对应
- 收集
listeners
: 遍历fiber tree
, 收集所有监听本事件的listener
函数.
- 派发合成事件: 构造合成事件, 遍历
listeners
进行派发.
合成事件初始化
SimpleEventPlugin.registerEvents();
EnterLeaveEventPlugin.registerEvents();
ChangeEventPlugin.registerEvents();
SelectEventPlugin.registerEvents();
BeforeInputEventPlugin.registerEvents();
事件注册
React在启动时,会调用
createRootImpl
function createRootImpl(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
// ...省略
listenToAllSupportedEvents(rootContainerElement);
// ...省略
}
而
listenToAllSupportedEvents
,实际上完成了事件的注册,主要做了两件事:
- 防止重复监听的if优化
- 遍历
allNativeEvents
,调用listenToNativeEvent
监听冒泡和捕获阶段的事件
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
if (rootContainerElement[listeningMarker]) {
// 第一次是false
return;
}
rootContainerElement[listeningMarker] = true;
allNativeEvents.forEach(domEventName => {
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(
domEventName,
false, // false表示冒泡阶段的监听
((rootContainerElement: any): Element),
null,
);
}
listenToNativeEvent(
domEventName,
true, // true表示捕获阶段监听
((rootContainerElement: any): Element),
null,
);
});
}
继续看
listenToNativeEvent
,内部利用了Set
,保证了同样的事件只会被注册一次,通过调用addTrappedEventListener
注册了事件监听export function listenToNativeEvent(
domEventName: DOMEventName,
isCapturePhaseListener: boolean,
rootContainerElement: EventTarget,
targetElement: Element | null,
eventSystemFlags?: EventSystemFlags = 0,
): void {
let target = rootContainerElement;
if (
domEventName === 'selectionchange' &&
(rootContainerElement: any).nodeType !== DOCUMENT_NODE
) {
target = (rootContainerElement: any).ownerDocument;
}
if (
targetElement !== null &&
!isCapturePhaseListener &&
nonDelegatedEvents.has(domEventName)
) {
if (domEventName !== 'scroll') {
return;
}
eventSystemFlags |= IS_NON_DELEGATED;
target = targetElement;
}
// 给dom设置一个属性值(new Set()),如果已有则返回原先的
const listenerSet = getEventListenerSet(target);
// 根据domEventName和是否是捕获(isCapturePhaseListener)
// 生成最终的eventName,如cancel__capture或cancel__bubble
const listenerSetKey = getListenerSetKey(
domEventName,
isCapturePhaseListener,
);
if (!listenerSet.has(listenerSetKey)) {
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE;
}
addTrappedEventListener(
target,
domEventName,
eventSystemFlags,
isCapturePhaseListener,
);
listenerSet.add(listenerSetKey);
}
}
addTrappedEventListener
首先会调用createEventListenerWrapperWithPriority
根据事件的优先级创建监听器,之后会根据是冒泡或捕获,用调用对应的事件注册函数function addTrappedEventListener(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
isCapturePhaseListener: boolean,
isDeferredListenerForLegacyFBSupport?: boolean,
) {
// 根据事件优先级创建事件监听器wrapper(bind)
let listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
);
let isPassiveListener = undefined;
// passive用于浏览器优化页面滚动性能,除了在注册事件时多一个passive:true属性,没别的区别
if (passiveBrowserEventsSupported) {
if (
domEventName === 'touchstart' ||
domEventName === 'touchmove' ||
domEventName === 'wheel'
) {
isPassiveListener = true;
}
}
let unsubscribeListener;
// 注册事件监听
if (isCapturePhaseListener) {
if (isPassiveListener !== undefined) {
unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
targetContainer,
domEventName,
listener,
isPassiveListener,
);
} else {
unsubscribeListener = addEventCaptureListener(
targetContainer,
domEventName,
listener,
);
}
} else {
if (isPassiveListener !== undefined) {
unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
targetContainer,
domEventName,
listener,
isPassiveListener,
);
} else {
unsubscribeListener = addEventBubbleListener(
targetContainer,
domEventName,
listener,
);
}
}
}
export function addEventCaptureListener(
target: EventTarget,
eventType: string,
listener: Function,
): Function {
target.addEventListener(eventType, listener, true);
return listener;
}
export function addEventBubbleListener(
target: EventTarget,
eventType: string,
listener: Function,
): Function {
target.addEventListener(eventType, listener, false);
return listener;
}
在
createEventListenerWrapperWithPriority
中,会根据DOM事件名拿到事件的优先级的高低(离散,用户阻塞,连续),来调用不同的listenerWrapper
- DiscreteEvent: click, keydown, input 对应
dispatchDiscreteEvent
- UserBlockingEvent: drag, scroll 对应
dispatchUserBlockingUpdate
- ContinuousEvent: play, load 对应
dispatchEvent
export function createEventListenerWrapperWithPriority(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
): Function {
// 根据dom事件名获取时间优先级
const eventPriority = getEventPriorityForPluginSystem(domEventName);
let listenerWrapper;
switch (eventPriority) {
// 0:离散事件,如click,优先级低
case DiscreteEvent:
listenerWrapper = dispatchDiscreteEvent;
break;
// 1: 用户阻塞事件,如scroll,优先级中
case UserBlockingEvent:
listenerWrapper = dispatchUserBlockingUpdate;
break;
// 2:连续事件,如load, 优先级高
case ContinuousEvent:
default:
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(
null,
domEventName,
eventSystemFlags,
targetContainer,
);
}
事件触发
当原生事件被触发后,进入
dispatchEvent
,整个链路就长了。export function dispatchEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
): void {
if (!_enabled) {
return;
}
let allowReplay = true;
allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0;
if (
allowReplay &&
hasQueuedDiscreteEvents() &&
isReplayableDiscreteEvent(domEventName)
) {
queueDiscreteEvent(
null, // Flags that we're not actually blocked on anything as far as we know.
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
return;
}
const blockedOn = attemptToDispatchEvent(
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
if (blockedOn === null) {
// We successfully dispatched this event.
if (allowReplay) {
clearIfContinuousEvent(domEventName, nativeEvent);
}
return;
}
if (allowReplay) {
if (isReplayableDiscreteEvent(domEventName)) {
queueDiscreteEvent(
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
return;
}
if (
queueIfContinuousEvent(
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
)
) {
return;
}
clearIfContinuousEvent(domEventName, nativeEvent);
}
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
null,
targetContainer,
);
}
首先进入
attemptToDispatchEvent
,主要做的是拿到原生DOM节点,和该节点对应的fiber,调用dispatchEventForPluginEventSystem
通过插件系统,触发事件export function attemptToDispatchEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
): null | Container | SuspenseInstance {
// TODO: Warn if _enabled is false.
// 获取原生dom节点,一般是nativeEvent.target
const nativeEventTarget = getEventTarget(nativeEvent);
// 获取原生dom节点对应的fiber
let targetInst = getClosestInstanceFromNode(nativeEventTarget);
if (targetInst !== null) {
const nearestMounted = getNearestMountedFiber(targetInst);
// tree已经被卸载
if (nearestMounted === null) {
targetInst = null;
} else {
const tag = nearestMounted.tag;
if (tag === SuspenseComponent) {
const instance = getSuspenseInstanceFromFiber(nearestMounted);
if (instance !== null) {
return instance;
}
targetInst = null;
} else if (tag === HostRoot) {
const root: FiberRoot = nearestMounted.stateNode;
if (root.hydrate) {
return getContainerFromFiber(nearestMounted);
}
targetInst = null;
} else if (nearestMounted !== targetInst) {
targetInst = null;
}
}
}
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer,
);
// We're not blocked on anything.
return null;
}
dispatchEventForPluginEventSystem
内部,通过一个向上的循环,找到当前rootContainer
的fiber
,进入dispatchEventsForPlugins
export function dispatchEventForPluginEventSystem(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
let ancestorInst = targetInst;
if (
(eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 && // IS_EVENT_HANDLE_NON_MANAGED_NODE = 1
(eventSystemFlags & IS_NON_DELEGATED) === 0 // IS_NON_DELEGATED = 2
) {
const targetContainerNode = ((targetContainer: any): Node);
if (targetInst !== null) {
let node = targetInst;
mainLoop: while (true) {
if (node === null) {
return;
}
const nodeTag = node.tag;
if (nodeTag === HostRoot || nodeTag === HostPortal) {
let container = node.stateNode.containerInfo;
if (isMatchingRootContainer(container, targetContainerNode)) {
break;
}
if (nodeTag === HostPortal) {
let grandNode = node.return;
while (grandNode !== null) {
const grandTag = grandNode.tag;
if (grandTag === HostRoot || grandTag === HostPortal) {
const grandContainer = grandNode.stateNode.containerInfo;
if (
isMatchingRootContainer(grandContainer, targetContainerNode)
) {
return;
}
}
grandNode = grandNode.return;
}
}
while (container !== null) {
const parentNode = getClosestInstanceFromNode(container);
if (parentNode === null) {
return;
}
const parentTag = parentNode.tag;
if (parentTag === HostComponent || parentTag === HostText) {
node = ancestorInst = parentNode;
continue mainLoop;
}
container = container.parentNode;
}
}
node = node.return;
}
}
}
batchedEventUpdates(() =>
dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
ancestorInst,
targetContainer,
),
);
}
在
dispatchEventsForPlugins
内部,会调用extractEvents
,对事件对象进行合成,收集事件到执行路径上,并调用processDispatchQueue
执行收集到组件中真正的事件function dispatchEventsForPlugins(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
// 一般是nativeEvent.target
const nativeEventTarget = getEventTarget(nativeEvent);
const dispatchQueue: DispatchQueue = [];
// 事件对象的合成,收集事件到执行路径上
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
// 执行收集到的组件中真正的事件
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
由于其余
EventPlugin
都基于SimpleEventPlugin
封装,此处只对SimpleEventPlugin
展开function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
): void {
// 获取domEventName对应的ReactEventName,如click => onClick
const reactName = topLevelEventsToReactNames.get(domEventName);
if (reactName === undefined) {
return;
}
let SyntheticEventCtor = SyntheticEvent;
let reactEventType: string = domEventName;
// ...省略 根据domEventName,将SyntheticEventCtor赋值为不同的事件类型
// IS_CAPTURE_PHASE = 1 << 2 = 0b0100
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
// scroll事件不冒泡
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
中,会收集fiber
中所有listener
的回调。完成后,会构造React合成事件,并将其推入dispatchQueue
派发队列中。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;
// 从目标元素开始一直到root,累加所有的fiber对象和事件监听。
while (instance !== null) {
const {stateNode, tag} = instance;
if (tag === HostComponent && stateNode !== null) {
lastHostComponent = stateNode;
// Standard React on* listeners, i.e. onClick or onClickCapture
if (reactEventName !== null) {
// instance.stateNode上有pendingProps,如onClick
const listener = getListener(instance, reactEventName);
if (listener != null) {
listeners.push(
createDispatchListener(instance, listener, lastHostComponent),
);
}
}
}
// 如果只收集目标节点的话,那么就不用再往上收集了,直接跳出
if (accumulateTargetOnly) {
break;
}
instance = instance.return;
}
return listeners;
}
对事件对象进行合成,收集事件到执行路径上完成后,走到
processDiaptchQueue
,执行收集到组件中真正的事件export function processDispatchQueue(
dispatchQueue: DispatchQueue,
eventSystemFlags: EventSystemFlags,
): void {
// 是否是捕获阶段
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
for (let i = 0; i < dispatchQueue.length; i++) {
// 从dispatchQueue中取出事件对象和事件监听数组
const {event, listeners} = dispatchQueue[i];
// 将事件监听交由processDispatchQueueItemsInOrder去触发,同时传入事件对象供事件监听使用
processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
}
rethrowCaughtError();
}
processDispatchQueueItemsInOrder
内部,会根据是冒泡或捕获,正序或倒序遍历listeners
(不难理解,收集diapstchListeners时,是从当前fiber遍历至root,所以正序遍历是冒泡的顺序),依次调用executeDispatch
派发事件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];
// onClick = e => e.stopPropagation()后
// event的isPropagationStopped = functionThatReturnsTrue = () => true
// 那么就不会在调用后面的listener了,下面的冒泡同理
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;
}
}
}
executeDispatch
内部很简单,将currentTarget执行真正的事件函数后,再讲event.currentTarget
重置为null
function executeDispatch(
event: ReactSyntheticEvent,
listener: Function,
currentTarget: EventTarget,
): void {
const type = event.type || 'unknown-event';
// 将currentTarget,运行真正的事件函数后,如onClick后,再将event.currentTarget置为null
event.currentTarget = currentTarget;
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
event.currentTarget = null;
}
绑定事件
- diff时,通过registrationNameModule判断props是否有事件类型
props: {
className: "btn-primary",
onClick: function ..., // 事件类型
onBlur: function ... //事件类型
}
// registrationNameModule
{
onBlur: SimpleEventPlugin,
onClick: SimpleEventPlugin,
onClickCapture: SimpleEventPlugin,
onChange: ChangeEventPlugin,
onChangeCapture: ChangeEventPlugin,
onMouseEnter: EnterLeaveEventPlugin,
onMouseLeave: EnterLeaveEventPlugin,
...
}
- 通过registrationNameDependencies检查事件依赖了哪些原生事件
// registrationNameDependencies
{
onBlur: ['blur'],
onClick: ['click'],
onClickCapture: ['click'],
onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'],
onMouseEnter: ['mouseout', 'mouseover'],
onMouseLeave: ['mouseout', 'mouseover'],
...
}
- 检查这个事件是否注册过,如果没有,就挂在document上,将React提供的dispatchEvent回调
触发事件
- 任意一个事件触发,执行
dispatchEvent
函数。
dispatchEvent
执行batchedEventUpdates(handleTopLevel)
, batchedEventUpdates 会打开批量渲染开关并调用handleTopLevel
。
- handleTopLevel 会依次执行 plugins 里所有的事件插件。
- 如果一个插件检测到自己需要处理的事件类型时,则处理该事件。
注
- React 的合成事件只能在事件周期内使用,因为这个对象很可能被其他阶段复用, 如果想持久化需要手动调用
event.persist()
告诉 React 这个对象需要持久化。( React17 中被废弃)
- React 的冒泡和捕获并不是真正 DOM 级别的冒泡和捕获
- React 会在一个原生事件里触发所有相关节点的
onClick
事件, 在执行这些onClick
之前 React 会打开批量渲染开关,这个开关会将所有的setState
变成异步函数。
- 事件只针对原生组件生效,自定义组件不会触发
onClick
。
const handleClick = e => {
setTimeout(()=>{
console.log(e.target.value)
},100)
} //错误,因为事件处理完后已经销毁
// React在派发事件时会批量更新,所有的setState都会变成异步的
// 1,2 异步,render1次 3,4同步,render2次
const handleClick = () => {
setState({num: 1});
setState({num: 2});
setTimeout(()=>{
setState({num: 3});
setState({num: 4});
},100)
}
React17
调整将顶层事件绑定到container上,而不是document上
本文基于React17.0.0
在React中,事件通过委托挂载到
root
(根DOM容器)上。但并不是所有的事件都是这样处理的,一些特殊情况会通过listenToNonDelegatedEvents
直接绑定到触发DOM元素上,如scroll
、load
、selectionchange
等。这些事件除了监听的DOM不同,别的基本没区别。React的合成事件,将原生事件与fiber链接了起来,从实现上分为:
- 监听原生事件: 将DOM元素和
fiber
对应
- 收集
listeners
: 遍历fiber tree
, 收集所有监听本事件的listener
函数.
- 派发合成事件: 构造合成事件, 遍历
listeners
进行派发.
合成事件初始化
SimpleEventPlugin.registerEvents();
EnterLeaveEventPlugin.registerEvents();
ChangeEventPlugin.registerEvents();
SelectEventPlugin.registerEvents();
BeforeInputEventPlugin.registerEvents();
事件注册
React在启动时,会调用
createRootImpl
function createRootImpl(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
// ...省略
listenToAllSupportedEvents(rootContainerElement);
// ...省略
}
而
listenToAllSupportedEvents
,实际上完成了事件的注册,主要做了两件事:
- 防止重复监听的if优化
- 遍历
allNativeEvents
,调用listenToNativeEvent
监听冒泡和捕获阶段的事件
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
if (rootContainerElement[listeningMarker]) {
// 第一次是false
return;
}
rootContainerElement[listeningMarker] = true;
allNativeEvents.forEach(domEventName => {
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(
domEventName,
false, // false表示冒泡阶段的监听
((rootContainerElement: any): Element),
null,
);
}
listenToNativeEvent(
domEventName,
true, // true表示捕获阶段监听
((rootContainerElement: any): Element),
null,
);
});
}
继续看
listenToNativeEvent
,内部利用了Set
,保证了同样的事件只会被注册一次,通过调用addTrappedEventListener
注册了事件监听export function listenToNativeEvent(
domEventName: DOMEventName,
isCapturePhaseListener: boolean,
rootContainerElement: EventTarget,
targetElement: Element | null,
eventSystemFlags?: EventSystemFlags = 0,
): void {
let target = rootContainerElement;
if (
domEventName === 'selectionchange' &&
(rootContainerElement: any).nodeType !== DOCUMENT_NODE
) {
target = (rootContainerElement: any).ownerDocument;
}
if (
targetElement !== null &&
!isCapturePhaseListener &&
nonDelegatedEvents.has(domEventName)
) {
if (domEventName !== 'scroll') {
return;
}
eventSystemFlags |= IS_NON_DELEGATED;
target = targetElement;
}
// 给dom设置一个属性值(new Set()),如果已有则返回原先的
const listenerSet = getEventListenerSet(target);
// 根据domEventName和是否是捕获(isCapturePhaseListener)
// 生成最终的eventName,如cancel__capture或cancel__bubble
const listenerSetKey = getListenerSetKey(
domEventName,
isCapturePhaseListener,
);
if (!listenerSet.has(listenerSetKey)) {
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE;
}
addTrappedEventListener(
target,
domEventName,
eventSystemFlags,
isCapturePhaseListener,
);
listenerSet.add(listenerSetKey);
}
}
addTrappedEventListener
首先会调用createEventListenerWrapperWithPriority
根据事件的优先级创建监听器,之后会根据是冒泡或捕获,用调用对应的事件注册函数function addTrappedEventListener(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
isCapturePhaseListener: boolean,
isDeferredListenerForLegacyFBSupport?: boolean,
) {
// 根据事件优先级创建事件监听器wrapper(bind)
let listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
);
let isPassiveListener = undefined;
// passive用于浏览器优化页面滚动性能,除了在注册事件时多一个passive:true属性,没别的区别
if (passiveBrowserEventsSupported) {
if (
domEventName === 'touchstart' ||
domEventName === 'touchmove' ||
domEventName === 'wheel'
) {
isPassiveListener = true;
}
}
let unsubscribeListener;
// 注册事件监听
if (isCapturePhaseListener) {
if (isPassiveListener !== undefined) {
unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
targetContainer,
domEventName,
listener,
isPassiveListener,
);
} else {
unsubscribeListener = addEventCaptureListener(
targetContainer,
domEventName,
listener,
);
}
} else {
if (isPassiveListener !== undefined) {
unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
targetContainer,
domEventName,
listener,
isPassiveListener,
);
} else {
unsubscribeListener = addEventBubbleListener(
targetContainer,
domEventName,
listener,
);
}
}
}
export function addEventCaptureListener(
target: EventTarget,
eventType: string,
listener: Function,
): Function {
target.addEventListener(eventType, listener, true);
return listener;
}
export function addEventBubbleListener(
target: EventTarget,
eventType: string,
listener: Function,
): Function {
target.addEventListener(eventType, listener, false);
return listener;
}
在
createEventListenerWrapperWithPriority
中,会根据DOM事件名拿到事件的优先级的高低(离散,用户阻塞,连续),来调用不同的listenerWrapper
- DiscreteEvent: click, keydown, input 对应
dispatchDiscreteEvent
- UserBlockingEvent: drag, scroll 对应
dispatchUserBlockingUpdate
- ContinuousEvent: play, load 对应
dispatchEvent
export function createEventListenerWrapperWithPriority(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
): Function {
// 根据dom事件名获取时间优先级
const eventPriority = getEventPriorityForPluginSystem(domEventName);
let listenerWrapper;
switch (eventPriority) {
// 0:离散事件,如click,优先级低
case DiscreteEvent:
listenerWrapper = dispatchDiscreteEvent;
break;
// 1: 用户阻塞事件,如scroll,优先级中
case UserBlockingEvent:
listenerWrapper = dispatchUserBlockingUpdate;
break;
// 2:连续事件,如load, 优先级高
case ContinuousEvent:
default:
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(
null,
domEventName,
eventSystemFlags,
targetContainer,
);
}
事件触发
当原生事件被触发后,进入
dispatchEvent
,整个链路就长了。export function dispatchEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
): void {
if (!_enabled) {
return;
}
let allowReplay = true;
allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0;
if (
allowReplay &&
hasQueuedDiscreteEvents() &&
isReplayableDiscreteEvent(domEventName)
) {
queueDiscreteEvent(
null, // Flags that we're not actually blocked on anything as far as we know.
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
return;
}
const blockedOn = attemptToDispatchEvent(
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
if (blockedOn === null) {
// We successfully dispatched this event.
if (allowReplay) {
clearIfContinuousEvent(domEventName, nativeEvent);
}
return;
}
if (allowReplay) {
if (isReplayableDiscreteEvent(domEventName)) {
queueDiscreteEvent(
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
return;
}
if (
queueIfContinuousEvent(
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
)
) {
return;
}
clearIfContinuousEvent(domEventName, nativeEvent);
}
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
null,
targetContainer,
);
}
首先进入
attemptToDispatchEvent
,主要做的是拿到原生DOM节点,和该节点对应的fiber,调用dispatchEventForPluginEventSystem
通过插件系统,触发事件export function attemptToDispatchEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
): null | Container | SuspenseInstance {
// TODO: Warn if _enabled is false.
// 获取原生dom节点,一般是nativeEvent.target
const nativeEventTarget = getEventTarget(nativeEvent);
// 获取原生dom节点对应的fiber
let targetInst = getClosestInstanceFromNode(nativeEventTarget);
if (targetInst !== null) {
const nearestMounted = getNearestMountedFiber(targetInst);
// tree已经被卸载
if (nearestMounted === null) {
targetInst = null;
} else {
const tag = nearestMounted.tag;
if (tag === SuspenseComponent) {
const instance = getSuspenseInstanceFromFiber(nearestMounted);
if (instance !== null) {
return instance;
}
targetInst = null;
} else if (tag === HostRoot) {
const root: FiberRoot = nearestMounted.stateNode;
if (root.hydrate) {
return getContainerFromFiber(nearestMounted);
}
targetInst = null;
} else if (nearestMounted !== targetInst) {
targetInst = null;
}
}
}
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer,
);
// We're not blocked on anything.
return null;
}
dispatchEventForPluginEventSystem
内部,通过一个向上的循环,找到当前rootContainer
的fiber
,进入dispatchEventsForPlugins
export function dispatchEventForPluginEventSystem(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
let ancestorInst = targetInst;
if (
(eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 && // IS_EVENT_HANDLE_NON_MANAGED_NODE = 1
(eventSystemFlags & IS_NON_DELEGATED) === 0 // IS_NON_DELEGATED = 2
) {
const targetContainerNode = ((targetContainer: any): Node);
if (targetInst !== null) {
let node = targetInst;
mainLoop: while (true) {
if (node === null) {
return;
}
const nodeTag = node.tag;
if (nodeTag === HostRoot || nodeTag === HostPortal) {
let container = node.stateNode.containerInfo;
if (isMatchingRootContainer(container, targetContainerNode)) {
break;
}
if (nodeTag === HostPortal) {
let grandNode = node.return;
while (grandNode !== null) {
const grandTag = grandNode.tag;
if (grandTag === HostRoot || grandTag === HostPortal) {
const grandContainer = grandNode.stateNode.containerInfo;
if (
isMatchingRootContainer(grandContainer, targetContainerNode)
) {
return;
}
}
grandNode = grandNode.return;
}
}
while (container !== null) {
const parentNode = getClosestInstanceFromNode(container);
if (parentNode === null) {
return;
}
const parentTag = parentNode.tag;
if (parentTag === HostComponent || parentTag === HostText) {
node = ancestorInst = parentNode;
continue mainLoop;
}
container = container.parentNode;
}
}
node = node.return;
}
}
}
batchedEventUpdates(() =>
dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
ancestorInst,
targetContainer,
),
);
}
在
dispatchEventsForPlugins
内部,会调用extractEvents
,对事件对象进行合成,收集事件到执行路径上,并调用processDispatchQueue
执行收集到组件中真正的事件function dispatchEventsForPlugins(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
// 一般是nativeEvent.target
const nativeEventTarget = getEventTarget(nativeEvent);
const dispatchQueue: DispatchQueue = [];
// 事件对象的合成,收集事件到执行路径上
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
// 执行收集到的组件中真正的事件
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
由于其余
EventPlugin
都基于SimpleEventPlugin
封装,此处只对SimpleEventPlugin
展开function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
): void {
// 获取domEventName对应的ReactEventName,如click => onClick
const reactName = topLevelEventsToReactNames.get(domEventName);
if (reactName === undefined) {
return;
}
let SyntheticEventCtor = SyntheticEvent;
let reactEventType: string = domEventName;
// ...省略 根据domEventName,将SyntheticEventCtor赋值为不同的事件类型
// IS_CAPTURE_PHASE = 1 << 2 = 0b0100
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
// scroll事件不冒泡
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
中,会收集fiber
中所有listener
的回调。完成后,会构造React合成事件,并将其推入dispatchQueue
派发队列中。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;
// 从目标元素开始一直到root,累加所有的fiber对象和事件监听。
while (instance !== null) {
const {stateNode, tag} = instance;
if (tag === HostComponent && stateNode !== null) {
lastHostComponent = stateNode;
// Standard React on* listeners, i.e. onClick or onClickCapture
if (reactEventName !== null) {
// instance.stateNode上有pendingProps,如onClick
const listener = getListener(instance, reactEventName);
if (listener != null) {
listeners.push(
createDispatchListener(instance, listener, lastHostComponent),
);
}
}
}
// 如果只收集目标节点的话,那么就不用再往上收集了,直接跳出
if (accumulateTargetOnly) {
break;
}
instance = instance.return;
}
return listeners;
}
对事件对象进行合成,收集事件到执行路径上完成后,走到
processDiaptchQueue
,执行收集到组件中真正的事件export function processDispatchQueue(
dispatchQueue: DispatchQueue,
eventSystemFlags: EventSystemFlags,
): void {
// 是否是捕获阶段
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
for (let i = 0; i < dispatchQueue.length; i++) {
// 从dispatchQueue中取出事件对象和事件监听数组
const {event, listeners} = dispatchQueue[i];
// 将事件监听交由processDispatchQueueItemsInOrder去触发,同时传入事件对象供事件监听使用
processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
}
rethrowCaughtError();
}
processDispatchQueueItemsInOrder
内部,会根据是冒泡或捕获,正序或倒序遍历listeners
(不难理解,收集diapstchListeners时,是从当前fiber遍历至root,所以正序遍历是冒泡的顺序),依次调用executeDispatch
派发事件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];
// onClick = e => e.stopPropagation()后
// event的isPropagationStopped = functionThatReturnsTrue = () => true
// 那么就不会在调用后面的listener了,下面的冒泡同理
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;
}
}
}
executeDispatch
内部很简单,将currentTarget执行真正的事件函数后,再讲event.currentTarget
重置为null
function executeDispatch(
event: ReactSyntheticEvent,
listener: Function,
currentTarget: EventTarget,
): void {
const type = event.type || 'unknown-event';
// 将currentTarget,运行真正的事件函数后,如onClick后,再将event.currentTarget置为null
event.currentTarget = currentTarget;
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
event.currentTarget = null;
}
本文基于React17.0.0
在React中,事件通过委托挂载到
root
(根DOM容器)上。但并不是所有的事件都是这样处理的,一些特殊情况会通过listenToNonDelegatedEvents
直接绑定到触发DOM元素上,如scroll
、load
、selectionchange
等。这些事件除了监听的DOM不同,别的基本没区别。React的合成事件,将原生事件与fiber链接了起来,从实现上分为:
- 监听原生事件: 将DOM元素和
fiber
对应
- 收集
listeners
: 遍历fiber tree
, 收集所有监听本事件的listener
函数.
- 派发合成事件: 构造合成事件, 遍历
listeners
进行派发.
合成事件初始化
SimpleEventPlugin.registerEvents();
EnterLeaveEventPlugin.registerEvents();
ChangeEventPlugin.registerEvents();
SelectEventPlugin.registerEvents();
BeforeInputEventPlugin.registerEvents();
事件注册
React在启动时,会调用
createRootImpl
function createRootImpl(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
// ...省略
listenToAllSupportedEvents(rootContainerElement);
// ...省略
}
而
listenToAllSupportedEvents
,实际上完成了事件的注册,主要做了两件事:
- 防止重复监听的if优化
- 遍历
allNativeEvents
,调用listenToNativeEvent
监听冒泡和捕获阶段的事件
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
if (rootContainerElement[listeningMarker]) {
// 第一次是false
return;
}
rootContainerElement[listeningMarker] = true;
allNativeEvents.forEach(domEventName => {
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(
domEventName,
false, // false表示冒泡阶段的监听
((rootContainerElement: any): Element),
null,
);
}
listenToNativeEvent(
domEventName,
true, // true表示捕获阶段监听
((rootContainerElement: any): Element),
null,
);
});
}
继续看
listenToNativeEvent
,内部利用了Set
,保证了同样的事件只会被注册一次,通过调用addTrappedEventListener
注册了事件监听export function listenToNativeEvent(
domEventName: DOMEventName,
isCapturePhaseListener: boolean,
rootContainerElement: EventTarget,
targetElement: Element | null,
eventSystemFlags?: EventSystemFlags = 0,
): void {
let target = rootContainerElement;
if (
domEventName === 'selectionchange' &&
(rootContainerElement: any).nodeType !== DOCUMENT_NODE
) {
target = (rootContainerElement: any).ownerDocument;
}
if (
targetElement !== null &&
!isCapturePhaseListener &&
nonDelegatedEvents.has(domEventName)
) {
if (domEventName !== 'scroll') {
return;
}
eventSystemFlags |= IS_NON_DELEGATED;
target = targetElement;
}
// 给dom设置一个属性值(new Set()),如果已有则返回原先的
const listenerSet = getEventListenerSet(target);
// 根据domEventName和是否是捕获(isCapturePhaseListener)
// 生成最终的eventName,如cancel__capture或cancel__bubble
const listenerSetKey = getListenerSetKey(
domEventName,
isCapturePhaseListener,
);
if (!listenerSet.has(listenerSetKey)) {
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE;
}
addTrappedEventListener(
target,
domEventName,
eventSystemFlags,
isCapturePhaseListener,
);
listenerSet.add(listenerSetKey);
}
}
addTrappedEventListener
首先会调用createEventListenerWrapperWithPriority
根据事件的优先级创建监听器,之后会根据是冒泡或捕获,用调用对应的事件注册函数function addTrappedEventListener(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
isCapturePhaseListener: boolean,
isDeferredListenerForLegacyFBSupport?: boolean,
) {
// 根据事件优先级创建事件监听器wrapper(bind)
let listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
);
let isPassiveListener = undefined;
// passive用于浏览器优化页面滚动性能,除了在注册事件时多一个passive:true属性,没别的区别
if (passiveBrowserEventsSupported) {
if (
domEventName === 'touchstart' ||
domEventName === 'touchmove' ||
domEventName === 'wheel'
) {
isPassiveListener = true;
}
}
let unsubscribeListener;
// 注册事件监听
if (isCapturePhaseListener) {
if (isPassiveListener !== undefined) {
unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
targetContainer,
domEventName,
listener,
isPassiveListener,
);
} else {
unsubscribeListener = addEventCaptureListener(
targetContainer,
domEventName,
listener,
);
}
} else {
if (isPassiveListener !== undefined) {
unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
targetContainer,
domEventName,
listener,
isPassiveListener,
);
} else {
unsubscribeListener = addEventBubbleListener(
targetContainer,
domEventName,
listener,
);
}
}
}
export function addEventCaptureListener(
target: EventTarget,
eventType: string,
listener: Function,
): Function {
target.addEventListener(eventType, listener, true);
return listener;
}
export function addEventBubbleListener(
target: EventTarget,
eventType: string,
listener: Function,
): Function {
target.addEventListener(eventType, listener, false);
return listener;
}
在
createEventListenerWrapperWithPriority
中,会根据DOM事件名拿到事件的优先级的高低(离散,用户阻塞,连续),来调用不同的listenerWrapper
- DiscreteEvent: click, keydown, input 对应
dispatchDiscreteEvent
- UserBlockingEvent: drag, scroll 对应
dispatchUserBlockingUpdate
- ContinuousEvent: play, load 对应
dispatchEvent
export function createEventListenerWrapperWithPriority(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
): Function {
// 根据dom事件名获取时间优先级
const eventPriority = getEventPriorityForPluginSystem(domEventName);
let listenerWrapper;
switch (eventPriority) {
// 0:离散事件,如click,优先级低
case DiscreteEvent:
listenerWrapper = dispatchDiscreteEvent;
break;
// 1: 用户阻塞事件,如scroll,优先级中
case UserBlockingEvent:
listenerWrapper = dispatchUserBlockingUpdate;
break;
// 2:连续事件,如load, 优先级高
case ContinuousEvent:
default:
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(
null,
domEventName,
eventSystemFlags,
targetContainer,
);
}
事件触发
当原生事件被触发后,进入
dispatchEvent
,整个链路就长了。export function dispatchEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
): void {
if (!_enabled) {
return;
}
let allowReplay = true;
allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0;
if (
allowReplay &&
hasQueuedDiscreteEvents() &&
isReplayableDiscreteEvent(domEventName)
) {
queueDiscreteEvent(
null, // Flags that we're not actually blocked on anything as far as we know.
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
return;
}
const blockedOn = attemptToDispatchEvent(
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
if (blockedOn === null) {
// We successfully dispatched this event.
if (allowReplay) {
clearIfContinuousEvent(domEventName, nativeEvent);
}
return;
}
if (allowReplay) {
if (isReplayableDiscreteEvent(domEventName)) {
queueDiscreteEvent(
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
return;
}
if (
queueIfContinuousEvent(
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
)
) {
return;
}
clearIfContinuousEvent(domEventName, nativeEvent);
}
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
null,
targetContainer,
);
}
首先进入
attemptToDispatchEvent
,主要做的是拿到原生DOM节点,和该节点对应的fiber,调用dispatchEventForPluginEventSystem
通过插件系统,触发事件export function attemptToDispatchEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
): null | Container | SuspenseInstance {
// TODO: Warn if _enabled is false.
// 获取原生dom节点,一般是nativeEvent.target
const nativeEventTarget = getEventTarget(nativeEvent);
// 获取原生dom节点对应的fiber
let targetInst = getClosestInstanceFromNode(nativeEventTarget);
if (targetInst !== null) {
const nearestMounted = getNearestMountedFiber(targetInst);
// tree已经被卸载
if (nearestMounted === null) {
targetInst = null;
} else {
const tag = nearestMounted.tag;
if (tag === SuspenseComponent) {
const instance = getSuspenseInstanceFromFiber(nearestMounted);
if (instance !== null) {
return instance;
}
targetInst = null;
} else if (tag === HostRoot) {
const root: FiberRoot = nearestMounted.stateNode;
if (root.hydrate) {
return getContainerFromFiber(nearestMounted);
}
targetInst = null;
} else if (nearestMounted !== targetInst) {
targetInst = null;
}
}
}
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer,
);
// We're not blocked on anything.
return null;
}
dispatchEventForPluginEventSystem
内部,通过一个向上的循环,找到当前rootContainer
的fiber
,进入dispatchEventsForPlugins
export function dispatchEventForPluginEventSystem(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
let ancestorInst = targetInst;
if (
(eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 && // IS_EVENT_HANDLE_NON_MANAGED_NODE = 1
(eventSystemFlags & IS_NON_DELEGATED) === 0 // IS_NON_DELEGATED = 2
) {
const targetContainerNode = ((targetContainer: any): Node);
if (targetInst !== null) {
let node = targetInst;
mainLoop: while (true) {
if (node === null) {
return;
}
const nodeTag = node.tag;
if (nodeTag === HostRoot || nodeTag === HostPortal) {
let container = node.stateNode.containerInfo;
if (isMatchingRootContainer(container, targetContainerNode)) {
break;
}
if (nodeTag === HostPortal) {
let grandNode = node.return;
while (grandNode !== null) {
const grandTag = grandNode.tag;
if (grandTag === HostRoot || grandTag === HostPortal) {
const grandContainer = grandNode.stateNode.containerInfo;
if (
isMatchingRootContainer(grandContainer, targetContainerNode)
) {
return;
}
}
grandNode = grandNode.return;
}
}
while (container !== null) {
const parentNode = getClosestInstanceFromNode(container);
if (parentNode === null) {
return;
}
const parentTag = parentNode.tag;
if (parentTag === HostComponent || parentTag === HostText) {
node = ancestorInst = parentNode;
continue mainLoop;
}
container = container.parentNode;
}
}
node = node.return;
}
}
}
batchedEventUpdates(() =>
dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
ancestorInst,
targetContainer,
),
);
}
在
dispatchEventsForPlugins
内部,会调用extractEvents
,对事件对象进行合成,收集事件到执行路径上,并调用processDispatchQueue
执行收集到组件中真正的事件function dispatchEventsForPlugins(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
// 一般是nativeEvent.target
const nativeEventTarget = getEventTarget(nativeEvent);
const dispatchQueue: DispatchQueue = [];
// 事件对象的合成,收集事件到执行路径上
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
// 执行收集到的组件中真正的事件
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
由于其余
EventPlugin
都基于SimpleEventPlugin
封装,此处只对SimpleEventPlugin
展开function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
): void {
// 获取domEventName对应的ReactEventName,如click => onClick
const reactName = topLevelEventsToReactNames.get(domEventName);
if (reactName === undefined) {
return;
}
let SyntheticEventCtor = SyntheticEvent;
let reactEventType: string = domEventName;
// ...省略 根据domEventName,将SyntheticEventCtor赋值为不同的事件类型
// IS_CAPTURE_PHASE = 1 << 2 = 0b0100
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
// scroll事件不冒泡
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
中,会收集fiber
中所有listener
的回调。完成后,会构造React合成事件,并将其推入dispatchQueue
派发队列中。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;
// 从目标元素开始一直到root,累加所有的fiber对象和事件监听。
while (instance !== null) {
const {stateNode, tag} = instance;
if (tag === HostComponent && stateNode !== null) {
lastHostComponent = stateNode;
// Standard React on* listeners, i.e. onClick or onClickCapture
if (reactEventName !== null) {
// instance.stateNode上有pendingProps,如onClick
const listener = getListener(instance, reactEventName);
if (listener != null) {
listeners.push(
createDispatchListener(instance, listener, lastHostComponent),
);
}
}
}
// 如果只收集目标节点的话,那么就不用再往上收集了,直接跳出
if (accumulateTargetOnly) {
break;
}
instance = instance.return;
}
return listeners;
}
对事件对象进行合成,收集事件到执行路径上完成后,走到
processDiaptchQueue
,执行收集到组件中真正的事件export function processDispatchQueue(
dispatchQueue: DispatchQueue,
eventSystemFlags: EventSystemFlags,
): void {
// 是否是捕获阶段
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
for (let i = 0; i < dispatchQueue.length; i++) {
// 从dispatchQueue中取出事件对象和事件监听数组
const {event, listeners} = dispatchQueue[i];
// 将事件监听交由processDispatchQueueItemsInOrder去触发,同时传入事件对象供事件监听使用
processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
}
rethrowCaughtError();
}
processDispatchQueueItemsInOrder
内部,会根据是冒泡或捕获,正序或倒序遍历listeners
(不难理解,收集diapstchListeners时,是从当前fiber遍历至root,所以正序遍历是冒泡的顺序),依次调用executeDispatch
派发事件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];
// onClick = e => e.stopPropagation()后
// event的isPropagationStopped = functionThatReturnsTrue = () => true
// 那么就不会在调用后面的listener了,下面的冒泡同理
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;
}
}
}
executeDispatch
内部很简单,将currentTarget执行真正的事件函数后,再讲event.currentTarget
重置为null