跳到主要内容

DOM plugin event system

一、作用

二、导出类型

1. 调度队列

export type DispatchQueue = Array<DispatchEntry>;

备注
  • () 由 [] 实现
  • () 由 [] 提供
  • () 由 渲染平台提供

三、导出的常量

1. 媒体事件类型

// List of events that need to be individually attached to media elements.
// 需要单独附加到媒体元素的事件列表。
export const mediaEventTypes: Array<DOMEventName> = [
'abort',
'canplay',
'canplaythrough',
'durationchange',
'emptied',
'encrypted',
'ended',
'error',
'loadeddata',
'loadedmetadata',
'loadstart',
'pause',
'play',
'playing',
'progress',
'ratechange',
'resize',
'seeked',
'seeking',
'stalled',
'suspend',
'timeupdate',
'volumechange',
'waiting',
];

2. 未委托事件

// We should not delegate these events to the container, but rather
// set them on the actual target element itself. This is primarily
// because these events do not consistently bubble in the DOM.
// 我们不应该将这些事件委托给容器,而是将它们设置在实际的目标元素本身上。这主要是因为这些事件在
// DOM 中不会持续冒泡。
export const nonDelegatedEvents: Set<DOMEventName> = new Set([
'beforetoggle',
'cancel',
'close',
'invalid',
'load',
'scroll',
'scrollend',
'toggle',
// In order to reduce bytes, we insert the above array of media events
// into this Set. Note: the "error" event isn't an exclusive media event,
// and can occur on other elements too. Rather than duplicate that event,
// we just take it from the media events array.
// 为了减少字节,我们将上面的媒体事件数组插入到这个 Set 中。注意:“error” 事件并不是专属的媒
// 体事件,也可能出现在其他元素上。我们没有重复该事件,而是直接从媒体事件数组中获取它。
...mediaEventTypes,
]);

1.

备注
  • () 由 [] 实现
  • () 由 [] 提供
  • () 由 渲染平台提供

四、处理分发队列

备注
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);
// event system doesn't use pooling.
// 事件系统不使用对象池。
}
}

五、监听非委托事件

备注
export function listenToNonDelegatedEvent(
domEventName: DOMEventName,
targetElement: Element,
): void {
if (__DEV__) {
if (!nonDelegatedEvents.has(domEventName)) {
console.error(
'Did not expect a listenToNonDelegatedEvent() call for "%s". ' +
'This is a bug in React. Please file an issue.',
domEventName,
);
}
}
const isCapturePhaseListener = false;
const listenerSet = getEventListenerSet(targetElement);
const listenerSetKey = getListenerSetKey(
domEventName,
isCapturePhaseListener,
);
if (!listenerSet.has(listenerSetKey)) {
addTrappedEventListener(
targetElement,
domEventName,
IS_NON_DELEGATED,
isCapturePhaseListener,
);
listenerSet.add(listenerSetKey);
}
}

六、监听原生事件

备注
export function listenToNativeEvent(
domEventName: DOMEventName,
isCapturePhaseListener: boolean,
target: EventTarget,
): void {
if (__DEV__) {
if (nonDelegatedEvents.has(domEventName) && !isCapturePhaseListener) {
console.error(
'Did not expect a listenToNativeEvent() call for "%s" in the bubble phase. ' +
'This is a bug in React. Please file an issue.',
domEventName,
);
}
}

let eventSystemFlags = 0;
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE;
}
addTrappedEventListener(
target,
domEventName,
eventSystemFlags,
isCapturePhaseListener,
);
}

七、为非托管事件目标监听本地事件

备注
// This is only used by createEventHandle when the
// target is not a DOM element. E.g. window.
// 这仅在 target 不是 DOM 元素时由 createEventHandle 使用。例如 window
export function listenToNativeEventForNonManagedEventTarget(
domEventName: DOMEventName,
isCapturePhaseListener: boolean,
target: EventTarget,
): void {
let eventSystemFlags: number = IS_EVENT_HANDLE_NON_MANAGED_NODE;
const listenerSet = getEventListenerSet(target);
const listenerSetKey = getListenerSetKey(
domEventName,
isCapturePhaseListener,
);
if (!listenerSet.has(listenerSetKey)) {
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE;
}
addTrappedEventListener(
target,
domEventName,
eventSystemFlags,
isCapturePhaseListener,
);
listenerSet.add(listenerSetKey);
}
}

八、监听所有支持的事件

备注
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
if (!(rootContainerElement as any)[listeningMarker]) {
(rootContainerElement as any)[listeningMarker] = true;
allNativeEvents.forEach(domEventName => {
// We handle selectionchange separately because it
// doesn't bubble and needs to be on the document.
// 我们单独处理 selectionchange 因为它不会冒泡,需要在 document 上处理。
if (domEventName !== 'selectionchange') {
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(domEventName, false, rootContainerElement);
}
listenToNativeEvent(domEventName, true, rootContainerElement);
}
});
const ownerDocument =
(rootContainerElement as any).nodeType === DOCUMENT_NODE
? rootContainerElement
: (rootContainerElement as any).ownerDocument;
if (ownerDocument !== null) {
// The selectionchange event also needs deduplication
// but it is attached to the document.
// selectionchange 事件也需要去重,但它附加在文档上。
if (!(ownerDocument as any)[listeningMarker]) {
(ownerDocument as any)[listeningMarker] = true;
listenToNativeEvent('selectionchange', false, ownerDocument);
}
}
}
}

九、为插件事件系统分发事件

备注
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 &&
(eventSystemFlags & IS_NON_DELEGATED) === 0
) {
const targetContainerNode = targetContainer as any as Node;

// If we are using the legacy FB support flag, we
// defer the event to the null with a one
// time event listener so we can defer the event.
// 如果我们使用旧版 FB 支持标志,我们将事件延迟到 null,并使用一次性事件监听器,以便我们可
// 以延迟事件。
if (
enableLegacyFBSupport &&
// If our event flags match the required flags for entering
// FB legacy mode and we are processing the "click" event,
// then we can defer the event to the "document", to allow
// for legacy FB support, where the expected behavior was to
// match React < 16 behavior of delegated clicks to the doc.
// 如果我们的事件标志与进入 FB 旧模式所需的标志匹配,并且我们正在处理“click”事件,那么
// 我们可以将事件延迟到“document”,以支持旧版 FB,其预期行为是匹配 React < 16 的委托
// 点击到文档的行为。
domEventName === 'click' &&
(eventSystemFlags & SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE) === 0 &&
!isReplayingEvent(nativeEvent)
) {
deferClickToDocumentForLegacyFBSupport(domEventName, targetContainer);
return;
}
if (targetInst !== null) {
// The below logic attempts to work out if we need to change
// the target fiber to a different ancestor. We had similar logic
// in the legacy event system, except the big difference between
// systems is that the modern event system now has an event listener
// attached to each React Root and React Portal Root. Together,
// the DOM nodes representing these roots are the "rootContainer".
// To figure out which ancestor instance we should use, we traverse
// up the fiber tree from the target instance and attempt to find
// root boundaries that match that of our current "rootContainer".
// If we find that "rootContainer", we find the parent fiber
// sub-tree for that root and make that our ancestor instance.
// 以下逻辑尝试确定我们是否需要将目标 Fiber 更改为不同的祖先。我们在旧的事件系统中有类似
// 的逻辑,不同之处在于,现代事件系统现在在每个 React 根和 React Portal 根上都附加了
// 一个事件监听器。一起看,这些代表这些根的 DOM 节点就是“rootContainer”。为了确定我们
// 应该使用哪个祖先实例,我们从目标实例向上遍历 Fiber 树,并尝试找到与我们当前
// “rootContainer”匹配的根边界。如果我们找到了该“rootContainer”,我们找到该根的父
// Fiber 子树,并将其作为我们的祖先实例。
let node: null | Fiber = 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) {
// The target is a portal, but it's not the rootContainer we're looking for.
// Normally portals handle their own events all the way down to the root.
// So we should be able to stop now. However, we don't know if this portal
// was part of *our* root.
// 目标是一个 portal,但它不是我们正在寻找的 rootContainer。通常 portal 会一
// 直处理自己的事件直到 root。所以我们现在应该可以停止。然而,我们不知道这个
// portal 是否是 *我们的* root 的一部分。
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)
) {
// This is the rootContainer we're looking for and we found it as
// a parent of the Portal. That means we can ignore it because the
// Portal will bubble through to us.
// 这是我们要找的 rootContainer,我们在 Portal 的父容器中找到了它。那
// 意味着我们可以忽略它,因为 Portal 会冒泡到我们这里。
return;
}
}
grandNode = grandNode.return;
}
}
// Now we need to find it's corresponding host fiber in the other
// tree. To do this we can use getClosestInstanceFromNode, but we
// need to validate that the fiber is a host instance, otherwise
// we need to traverse up through the DOM till we find the correct
// node that is from the other tree.
// 现在我们需要找到它在另一个树中的对应宿主 fiber。为此我们可以使用
// `getClosestInstanceFromNode` ,但是我们需要验证该 fiber 是否是宿主实例,否
// 则我们需要沿着 DOM 向上遍历,直到找到来自另一个树的正确节点。
while (container !== null) {
const parentNode = getClosestInstanceFromNode(container);
if (parentNode === null) {
return;
}
const parentTag = parentNode.tag;
if (
parentTag === HostComponent ||
parentTag === HostText ||
parentTag === HostHoistable ||
parentTag === HostSingleton
) {
node = ancestorInst = parentNode;
continue mainLoop;
}
container = container.parentNode;
}
}
node = node.return;
}
}
}

batchedUpdates(() =>
dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
ancestorInst,
targetContainer,
),
);
}

十、累积单相监听器

备注
export function accumulateSinglePhaseListeners(
targetFiber: Fiber | null,
reactName: string | null,
nativeEventType: string,
inCapturePhase: boolean,
accumulateTargetOnly: boolean,
nativeEvent: AnyNativeEvent,
): Array<DispatchListener> {
const captureName = reactName !== null ? reactName + 'Capture' : null;
const reactEventName = inCapturePhase ? captureName : reactName;
let listeners: Array<DispatchListener> = [];

let instance = targetFiber;
let lastHostComponent = null;

// Accumulate all instances and listeners via the target -> root path.
// 通过目标 -> 根路径累积所有实例和监听器。
while (instance !== null) {
const { stateNode, tag } = instance;
// Handle listeners that are on HostComponents (i.e. <div>)
// 处理位于 HostComponents(即 <div>)上的监听器
if (
(tag === HostComponent ||
tag === HostHoistable ||
tag === HostSingleton) &&
stateNode !== null
) {
lastHostComponent = stateNode;

// createEventHandle listeners
// 创建事件处理程序监听器
if (enableCreateEventHandleAPI) {
const eventHandlerListeners =
getEventHandlerListeners(lastHostComponent);
if (eventHandlerListeners !== null) {
eventHandlerListeners.forEach(entry => {
if (
entry.type === nativeEventType &&
entry.capture === inCapturePhase
) {
listeners.push(
createDispatchListener(
instance,
entry.callback,
lastHostComponent as any,
),
);
}
});
}
}

// Standard React on* listeners, i.e. onClick or onClickCapture
// 标准的 React on* 监听器,例如 onClick 或 onClickCapture
if (reactEventName !== null) {
const listener = getListener(instance, reactEventName);
if (listener != null) {
listeners.push(
createDispatchListener(instance, listener, lastHostComponent),
);
}
}
} else if (
enableCreateEventHandleAPI &&
enableScopeAPI &&
tag === ScopeComponent &&
lastHostComponent !== null &&
stateNode !== null
) {
// Scopes
// 作用域
const reactScopeInstance = stateNode;
const eventHandlerListeners =
getEventHandlerListeners(reactScopeInstance);
if (eventHandlerListeners !== null) {
eventHandlerListeners.forEach(entry => {
if (
entry.type === nativeEventType &&
entry.capture === inCapturePhase
) {
listeners.push(
createDispatchListener(
instance,
entry.callback,
lastHostComponent as any,
),
);
}
});
}
}
// If we are only accumulating events for the target, then we don't
// continue to propagate through the React fiber tree to find other
// listeners.
// 如果我们只是为目标累积事件,那么我们不会继续通过 React fiber 树向下传播去寻找其他
// 监听器。
if (accumulateTargetOnly) {
break;
}
// If we are processing the onBeforeBlur event, then we need to take
// into consideration that part of the React tree might have been hidden
// or deleted (as we're invoking this event during commit). We can find
// this out by checking if intercept fiber set on the event matches the
// current instance fiber. In which case, we should clear all existing
// listeners.
// 如果我们正在处理 onBeforeBlur 事件,那么我们需要考虑到 React 树的一部分可能已经被隐
// 藏或删除(因为我们在提交时调用此事件)。我们可以通过检查事件上设置的拦截 fiber 是否与当
// 前实例 fiber 匹配来发现这一点。在这种情况下,我们应该清除所有现有的监听器。
if (enableCreateEventHandleAPI && nativeEvent.type === 'beforeblur') {
const detachedInterceptFiber = nativeEvent._detachedInterceptFiber;
if (
detachedInterceptFiber !== null &&
(detachedInterceptFiber === instance ||
detachedInterceptFiber === instance.alternate)
) {
listeners = [];
}
}
instance = instance.return;
}
return listeners;
}

十一、累积双阶段监听器

备注
// We should only use this function for:
// 我们应该仅在以下情况下使用此函数:
// - BeforeInputEventPlugin
// - ChangeEventPlugin
// - SelectEventPlugin
// - ScrollEndEventPlugin
// This is because we only process these plugins
// in the bubble phase, so we need to accumulate two
// phase event listeners (via emulation).
// 这是因为我们只在冒泡阶段处理这些插件,所以我们需要累积两阶段事件监听器(通过模拟)。
export function accumulateTwoPhaseListeners(
targetFiber: Fiber | null,
reactName: string,
): Array<DispatchListener> {
const captureName = reactName + 'Capture';
const listeners: Array<DispatchListener> = [];
let instance = targetFiber;

// Accumulate all instances and listeners via the target -> root path.
// 通过目标 -> 根路径累积所有实例和监听器。
while (instance !== null) {
const { stateNode, tag } = instance;
// Handle listeners that are on HostComponents (i.e. <div>)
// 处理位于 HostComponents(即 <div>)上的监听器
if (
(tag === HostComponent ||
tag === HostHoistable ||
tag === HostSingleton) &&
stateNode !== null
) {
const currentTarget = stateNode;
const captureListener = getListener(instance, captureName);
if (captureListener != null) {
listeners.unshift(
createDispatchListener(instance, captureListener, currentTarget),
);
}
const bubbleListener = getListener(instance, reactName);
if (bubbleListener != null) {
listeners.push(
createDispatchListener(instance, bubbleListener, currentTarget),
);
}
}
if (instance.tag === HostRoot) {
return listeners;
}
instance = instance.return;
}
// If we didn't reach the root it means we're unmounted and shouldn't
// dispatch any events on the target.
// 如果我们没有到达根节点,这意味着我们已经被卸载,不应该在目标上派发任何事件
return [];
}

十二、累积进入离开双阶段监听器

备注
// We should only use this function for:
// 我们应该只为以下情况使用此函数:
// - EnterLeaveEventPlugin
// This is because we only process this plugin
// in the bubble phase, so we need to accumulate two
// phase event listeners.
// 这是因为我们只在冒泡阶段处理此插件,所以我们需要累积两阶段的事件监听器。
export function accumulateEnterLeaveTwoPhaseListeners(
dispatchQueue: DispatchQueue,
leaveEvent: KnownReactSyntheticEvent,
enterEvent: null | KnownReactSyntheticEvent,
from: Fiber | null,
to: Fiber | null,
): void {
const common =
from && to ? getLowestCommonAncestor(from, to, getParent) : null;

if (from !== null) {
accumulateEnterLeaveListenersForEvent(
dispatchQueue,
leaveEvent,
from,
common,
false,
);
}
if (to !== null && enterEvent !== null) {
accumulateEnterLeaveListenersForEvent(
dispatchQueue,
enterEvent,
to,
common,
true,
);
}
}

十三、累积事件处理非托管节点监听器

备注
export function accumulateEventHandleNonManagedNodeListeners(
reactEventType: DOMEventName,
currentTarget: EventTarget,
inCapturePhase: boolean,
): Array<DispatchListener> {
const listeners: Array<DispatchListener> = [];

const eventListeners = getEventHandlerListeners(currentTarget);
if (eventListeners !== null) {
eventListeners.forEach(entry => {
if (entry.type === reactEventType && entry.capture === inCapturePhase) {
listeners.push(
createDispatchListener(null, entry.callback, currentTarget),
);
}
});
}
return listeners;
}

十四、获取监听器集合键

export function getListenerSetKey(
domEventName: DOMEventName,
capture: boolean,
): string {
return `${domEventName}__${capture ? 'capture' : 'bubble'}`;
}

十五、常量

1. 听力标记

备注

源码中 430 行

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

十六、工具

1. 顶级副作用

备注

源码中 92 - 100 行

// TODO: remove top-level side effect.
// 待办事项:移除顶层副作用。
SimpleEventPlugin.registerEvents();
EnterLeaveEventPlugin.registerEvents();
ChangeEventPlugin.registerEvents();
SelectEventPlugin.registerEvents();
BeforeInputEventPlugin.registerEvents();
if (enableScrollEndPolyfill) {
ScrollEndEventPlugin.registerEvents();
}

2. 提取事件

备注
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
) {
// TODO: we should remove the concept of a "SimpleEventPlugin".
// This is the basic functionality of the event system. All
// the other plugins are essentially polyfills. So the plugin
// should probably be inlined somewhere and have its logic
// be core the to event system. This would potentially allow
// us to ship builds of React without the polyfilled plugins below.
// TODO: 我们应该移除“SimpleEventPlugin”的概念。
// 这是事件系统的基本功能。其他所有插件本质上都是填充(polyfill)。
// 因此,该插件可能应该被内联到某处,并且其逻辑成为事件系统的核心。
// 这可能允许我们发布不包含下面这些填充插件的 React 构建版本。
SimpleEventPlugin.extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
const shouldProcessPolyfillPlugins =
(eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0;
// We don't process these events unless we are in the
// event's native "bubble" phase, which means that we're
// not in the capture phase. That's because we emulate
// the capture phase here still. This is a trade-off,
// because in an ideal world we would not emulate and use
// the phases properly, like we do with the SimpleEvent
// plugin. However, the plugins below either expect
// emulation (EnterLeave) or use state localized to that
// plugin (BeforeInput, Change, Select). The state in
// these modules complicates things, as you'll essentially
// get the case where the capture phase event might change
// state, only for the following bubble event to come in
// later and not trigger anything as the state now
// invalidates the heuristics of the event plugin. We
// could alter all these plugins to work in such ways, but
// that might cause other unknown side-effects that we
// can't foresee right now.
// 除非我们处于事件的原生“冒泡”阶段,否则我们不会处理这些事件,这意味着我们不在捕获阶段。因为我
// 们在这里仍然模拟捕获阶段。这是一种权衡,因为在理想情况下,我们不会模拟,而是像在
// SimpleEvent 插件中那样正确使用各个阶段。然而,下面的插件要么期望模拟(EnterLeave),要么
// 使用特定插件的本地状态(BeforeInput, Change, Select)。这些模块中的状态使事情变得复杂,
// 因为你实际上会遇到这样的情况:捕获阶段的事件可能会改变状态,而随后的冒泡事件在稍后到来时由于
// 状态已经使事件插件的启发式失效而不会触发任何操作。我们本可以修改所有这些插件以这种方式工作,
// 但这可能会导致我们现在无法预见的其他未知副作用。
if (shouldProcessPolyfillPlugins) {
EnterLeaveEventPlugin.extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
ChangeEventPlugin.extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
SelectEventPlugin.extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
BeforeInputEventPlugin.extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
FormActionEventPlugin.extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
}
if (enableScrollEndPolyfill) {
ScrollEndEventPlugin.extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
}
}

3. 执行调度

备注
function executeDispatch(
event: ReactSyntheticEvent,
listener: Function,
currentTarget: EventTarget,
): void {
event.currentTarget = currentTarget;
try {
listener(event);
} catch (error) {
reportGlobalError(error);
}
event.currentTarget = null;
}

4. 按顺序处理调度队列项

备注
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;
}
if (__DEV__ && instance !== null) {
runWithFiberInDEV(
instance,
executeDispatch,
event,
listener,
currentTarget,
);
} else {
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;
}
if (__DEV__ && instance !== null) {
runWithFiberInDEV(
instance,
executeDispatch,
event,
listener,
currentTarget,
);
} else {
executeDispatch(event, listener, currentTarget);
}
previousInstance = instance;
}
}
}

5. 为插件分发事件

备注
function dispatchEventsForPlugins(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
const nativeEventTarget = getEventTarget(nativeEvent);
const dispatchQueue: DispatchQueue = [];
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
processDispatchQueue(dispatchQueue, eventSystemFlags);
}

6. 添加捕获事件监听器

备注
function addTrappedEventListener(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
isCapturePhaseListener: boolean,
isDeferredListenerForLegacyFBSupport?: boolean,
) {
let listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
);
// If passive option is not supported, then the event will be
// active and not passive.
// 如果不支持被动选项,那么事件将是活跃的而非被动的。
let isPassiveListener: void | boolean = undefined;
if (passiveBrowserEventsSupported) {
// Browsers introduced an intervention, making these events
// passive by default on document. React doesn't bind them
// to document anymore, but changing this now would undo
// the performance wins from the change. So we emulate
// the existing behavior manually on the roots now.
// 浏览器引入了一种干预,使这些事件在 `document` 上默认是被动的。React 不再将它们绑定到
// `document` 上,但现在改变这一点会撤销此更改带来的性能提升。因此,我们现在在根节点上手动
// 模拟现有行为。
// https://github.com/facebook/react/issues/19651
if (
domEventName === 'touchstart' ||
domEventName === 'touchmove' ||
domEventName === 'wheel'
) {
isPassiveListener = true;
}
}

targetContainer =
enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport
? (targetContainer as any).ownerDocument
: targetContainer;

let unsubscribeListener;
// When legacyFBSupport is enabled, it's for when we
// want to add a one time event listener to a container.
// This should only be used with enableLegacyFBSupport
// due to requirement to provide compatibility with
// internal FB www event tooling. This works by removing
// the event listener as soon as it is invoked. We could
// also attempt to use the {once: true} param on
// addEventListener, but that requires support and some
// browsers do not support this today, and given this is
// to support legacy code patterns, it's likely they'll
// need support for such browsers.
// 当启用 legacyFBSupport 时,这是在我们想要向一个容器添加一次性事件监听器时使用的。这只应该
// 与 enableLegacyFBSupport 一起使用。因为需要提供与内部 FB www 事件工具的兼容性。它的工
// 作原理是在事件监听器被调用后立即移除它。我们也可以尝试在 addEventListener 上使用
// `{once: true}` 参数,但是这需要支持,并且某些浏览器今天不支持此功能,鉴于这是为了支持旧代
// 码模式,他们很可能需要支持这些浏览器。
if (enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport) {
const originalListener = listener;
listener = function (...p) {
removeEventListener(
targetContainer,
domEventName,
unsubscribeListener,
isCapturePhaseListener,
);
return originalListener.apply(this, p);
};
}
// TODO: There are too many combinations here. Consolidate them.
// 待办:这里的组合太多了。需要合并它们。
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,
);
}
}
}

7. 为旧版 FB 支持延迟点击到文档

备注
function deferClickToDocumentForLegacyFBSupport(
domEventName: DOMEventName,
targetContainer: EventTarget,
): void {
// We defer all click events with legacy FB support mode on.
// This means we add a one time event listener to trigger
// after the FB delegated listeners fire.
// 我们在启用旧版 FB 支持模式时延迟所有点击事件。这意味着我们添加一个一次性事件监听器,以在
// FB 委托的监听器触发后执行。
const isDeferredListenerForLegacyFBSupport = true;
addTrappedEventListener(
targetContainer,
domEventName,
IS_LEGACY_FB_SUPPORT_MODE,
false,
isDeferredListenerForLegacyFBSupport,
);
}

8. 判定是否是匹配根容器

备注
function isMatchingRootContainer(
grandContainer: Element,
targetContainer: EventTarget,
): boolean {
return (
grandContainer === targetContainer ||
(!disableCommentsAsDOMContainers &&
grandContainer.nodeType === COMMENT_NODE &&
grandContainer.parentNode === targetContainer)
);
}

9. 创建调度监听器

function createDispatchListener(
instance: null | Fiber,
listener: Function,
currentTarget: EventTarget,
): DispatchListener {
return {
instance,
listener,
currentTarget,
};
}

10. 获取父级

function getParent(inst: Fiber | null): Fiber | null {
if (inst === null) {
return null;
}
do {
inst = inst.return;
// TODO: If this is a HostRoot we might want to bail out.
// That is depending on if we want nested subtrees (layers) to bubble
// events to their parent. We could also go through parentNode on the
// host node but that wouldn't work for React Native and doesn't let us
// do the portal feature.
// 待办事项:如果这是一个 HostRoot,我们可能想要退出。
// 这取决于我们是否希望嵌套子树(层)将事件冒泡到它们的父级。我们也可以通过宿主节点的
// parentNode 来处理,但这在 React Native 中行不通,而且不允许我们使用 portal 功能。
} while (inst && inst.tag !== HostComponent && inst.tag !== HostSingleton);
if (inst) {
return inst;
}
return null;
}

11. 为事件积累进入和离开监听器

备注
function accumulateEnterLeaveListenersForEvent(
dispatchQueue: DispatchQueue,
event: KnownReactSyntheticEvent,
target: Fiber,
common: Fiber | null,
inCapturePhase: boolean,
): void {
const registrationName = event._reactName;
const listeners: Array<DispatchListener> = [];

let instance: null | Fiber = target;
while (instance !== null) {
if (instance === common) {
break;
}
const { alternate, stateNode, tag } = instance;
if (alternate !== null && alternate === common) {
break;
}
if (
(tag === HostComponent ||
tag === HostHoistable ||
tag === HostSingleton) &&
stateNode !== null
) {
const currentTarget = stateNode;
if (inCapturePhase) {
const captureListener = getListener(instance, registrationName);
if (captureListener != null) {
listeners.unshift(
createDispatchListener(instance, captureListener, currentTarget),
);
}
} else if (!inCapturePhase) {
const bubbleListener = getListener(instance, registrationName);
if (bubbleListener != null) {
listeners.push(
createDispatchListener(instance, bubbleListener, currentTarget),
);
}
}
}
instance = instance.return;
}
if (listeners.length !== 0) {
dispatchQueue.push({ event, listeners });
}
}

十七、类型

1. 调度监听器

// 调度监听器
type DispatchListener = {
instance: null | Fiber;
listener: Function;
currentTarget: EventTarget;
};

// 派遣条目
type DispatchEntry = {
event: ReactSyntheticEvent;
listeners: Array<DispatchListener>;
};