React DOM event replaying
一、作用
二、是需要补水的离散事件
export function isDiscreteEventThatRequiresHydration(
eventType: DOMEventName,
): boolean {
return discreteReplayableEvents.indexOf(eventType) > -1;
}
三、如果是连续事件则清除
// Resets the replaying for this type of continuous event to no event.
// 将此类型的连续事件的重放重置为无事件。
export function clearIfContinuousEvent(
domEventName: DOMEventName,
nativeEvent: AnyNativeEvent,
): void {
switch (domEventName) {
case 'focusin':
case 'focusout':
queuedFocus = null;
break;
case 'dragenter':
case 'dragleave':
queuedDrag = null;
break;
case 'mouseover':
case 'mouseout':
queuedMouse = null;
break;
case 'pointerover':
case 'pointerout': {
const pointerId = (nativeEvent as any as PointerEventType).pointerId;
queuedPointers.delete(pointerId);
break;
}
case 'gotpointercapture':
case 'lostpointercapture': {
const pointerId = (nativeEvent as any as PointerEventType).pointerId;
queuedPointerCaptures.delete(pointerId);
break;
}
}
}
四、如果是连续事件则排队
export function queueIfContinuousEvent(
blockedOn: null | Container | ActivityInstance | SuspenseInstance,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
): boolean {
// These set relatedTarget to null because the replayed event will be treated as if we
// moved from outside the window (no target) onto the target once it hydrates.
// Instead of mutating we could clone the event.
//
// 这些将 relatedTarget 设置为 null,因为重放的事件将被当作我们
// 从窗口外部(无目标)移动到目标上时触发,一旦它被挂载。
// 我们也可以选择克隆事件,而不是直接修改它。
switch (domEventName) {
case 'focusin': {
const focusEvent = nativeEvent as any as FocusEvent;
queuedFocus = accumulateOrCreateContinuousQueuedReplayableEvent(
queuedFocus,
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
focusEvent,
);
return true;
}
case 'dragenter': {
const dragEvent = nativeEvent as any as DragEvent;
queuedDrag = accumulateOrCreateContinuousQueuedReplayableEvent(
queuedDrag,
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
dragEvent,
);
return true;
}
case 'mouseover': {
const mouseEvent = nativeEvent as any as MouseEvent;
queuedMouse = accumulateOrCreateContinuousQueuedReplayableEvent(
queuedMouse,
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
mouseEvent,
);
return true;
}
case 'pointerover': {
const pointerEvent = nativeEvent as any as PointerEventType;
const pointerId = pointerEvent.pointerId;
queuedPointers.set(
pointerId,
accumulateOrCreateContinuousQueuedReplayableEvent(
queuedPointers.get(pointerId) || null,
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
pointerEvent,
),
);
return true;
}
case 'gotpointercapture': {
const pointerEvent = nativeEvent as any as PointerEventType;
const pointerId = pointerEvent.pointerId;
queuedPointerCaptures.set(
pointerId,
accumulateOrCreateContinuousQueuedReplayableEvent(
queuedPointerCaptures.get(pointerId) || null,
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
pointerEvent,
),
);
return true;
}
}
return false;
}
五、排队显式水化目标
export function queueExplicitHydrationTarget(target: Node): void {
const updatePriority = resolveUpdatePriority();
const queuedTarget: QueuedHydrationTarget = {
blockedOn: null,
target: target,
priority: updatePriority,
};
let i = 0;
for (; i < queuedExplicitHydrationTargets.length; i++) {
// Stop once we hit the first target with lower priority than
// 一旦我们击中第一个优先级较低的目标就停止
if (
!isHigherEventPriority(
updatePriority,
queuedExplicitHydrationTargets[i].priority,
)
) {
break;
}
}
queuedExplicitHydrationTargets.splice(i, 0, queuedTarget);
if (i === 0) {
attemptExplicitHydrationTarget(queuedTarget);
}
}
六、刷新事件重放
export function flushEventReplaying(): void {
// Synchronously flush any event replaying so that it gets observed before
// any new updates are applied.
//
// 同步刷新任何事件重放,以便在应用任何新更新之前被观察到。
if (hasScheduledReplayAttempt) {
replayUnblockedEvents();
}
}
七、队列变更事件
备注
enableHydrationChangeEvent由 ReactFeatureFlags#enableHydrationChangeEvent 提供
export function queueChangeEvent(target: EventTarget): void {
if (enableHydrationChangeEvent) {
queuedChangeEventTargets.push(target);
if (!hasScheduledReplayAttempt) {
hasScheduledReplayAttempt = true;
}
}
}
八、在被阻止时重试
备注
getFiberCurrentPropsFromNode()由 ReactDOMComponentTree 提供findInstanceBlockingTarget()由 ReactDOMEventListener 提供
export function retryIfBlockedOn(
unblocked: Container | SuspenseInstance | ActivityInstance,
): void {
if (queuedFocus !== null) {
scheduleCallbackIfUnblocked(queuedFocus, unblocked);
}
if (queuedDrag !== null) {
scheduleCallbackIfUnblocked(queuedDrag, unblocked);
}
if (queuedMouse !== null) {
scheduleCallbackIfUnblocked(queuedMouse, unblocked);
}
const unblock = (queuedEvent: QueuedReplayableEvent) =>
scheduleCallbackIfUnblocked(queuedEvent, unblocked);
queuedPointers.forEach(unblock);
queuedPointerCaptures.forEach(unblock);
for (let i = 0; i < queuedExplicitHydrationTargets.length; i++) {
const queuedTarget = queuedExplicitHydrationTargets[i];
if (queuedTarget.blockedOn === unblocked) {
queuedTarget.blockedOn = null;
}
}
while (queuedExplicitHydrationTargets.length > 0) {
const nextExplicitTarget = queuedExplicitHydrationTargets[0];
if (nextExplicitTarget.blockedOn !== null) {
// We're still blocked.
// 我们仍然被阻塞。
break;
} else {
attemptExplicitHydrationTarget(nextExplicitTarget);
if (nextExplicitTarget.blockedOn === null) {
// We're unblocked.
// 我们已经不受阻碍。
queuedExplicitHydrationTargets.shift();
}
}
}
// Check the document if there are any queued form actions.
// If there's no ownerDocument, then this is the document.
// 检查文档是否有排队的表单操作。
// 如果没有 ownerDocument,那么这就是文档本身。
const root = unblocked.ownerDocument || unblocked;
const formReplayingQueue: void | FormReplayingQueue = (root as any)
.$$reactFormReplay;
if (formReplayingQueue != null) {
for (let i = 0; i < formReplayingQueue.length; i += 3) {
const form: HTMLFormElement = formReplayingQueue[i];
const submitterOrAction:
| null
| HTMLInputElement
| HTMLButtonElement
| FormAction = formReplayingQueue[i + 1];
const formProps = getFiberCurrentPropsFromNode(form);
if (typeof submitterOrAction === 'function') {
// This action has already resolved. We're just waiting to dispatch it.
// 该操作已解决。我们只是等待将其分发。
if (!formProps) {
// This was not part of this React instance. It might have been recently
// unblocking us from dispatching our events. So let's make sure we schedule
// a retry.
// 这不是这个 React 实例的一部分。它可能最近刚取消阻止我们分发事件。因此我们需要确保安排重试。
scheduleReplayQueueIfNeeded(formReplayingQueue);
}
continue;
}
let target: Node = form;
if (formProps) {
// This form belongs to this React instance but the submitter might
// not be done yet.
// 这个表单属于这个 React 实例,但提交者可能还没有完成。
let action: null | FormAction = null;
const submitter = submitterOrAction;
if (submitter && submitter.hasAttribute('formAction')) {
// The submitter is the one that is responsible for the action.
// 提交者是对该操作负责的人。
target = submitter;
const submitterProps = getFiberCurrentPropsFromNode(submitter);
if (submitterProps) {
// The submitter is part of this instance.
// 提交者是此实例的一部分。
action = (submitterProps as any).formAction;
} else {
const blockedOn = findInstanceBlockingTarget(target);
if (blockedOn !== null) {
// The submitter is not hydrated yet. We'll wait for it.
// 提交者尚未补充水分。我们将等待它。
continue;
}
// The submitter must have been a part of a different React instance.
// Except the form isn't. We don't dispatch actions in this scenario.
// 提交者一定是属于另一个 React 实例的。
// 只是这个表单不是。在这种情况下,我们不会分发操作。
}
} else {
action = (formProps as any).action;
}
if (typeof action === 'function') {
formReplayingQueue[i + 1] = action;
} else {
// Something went wrong so let's just delete this action.
// 出了点问题,所以我们干脆删除这个操作。
formReplayingQueue.splice(i, 3);
i -= 3;
}
// Schedule a replay in case this unblocked something.
// 如果这解除了某些阻塞,请安排重放。
scheduleReplayQueueIfNeeded(formReplayingQueue);
continue;
}
// Something above this target is still blocked so we can't continue yet.
// We're not sure if this target is actually part of this React instance
// yet. It could be a different React as a child but at least some parent is.
// We must continue for any further queued actions.
// 目标上方的某些内容仍被阻止,所以我们还不能继续。
// 我们还不确定这个目标是否实际上属于这个 React 实例。
// 它可能是作为子元素的另一个 React,但至少有一些父元素是。
// 我们必须继续处理任何后续排队的操作。
}
}
}
常量
1. 排队指针
备注
该部分在源码中的 84 - 89 行
// For pointer events there can be one latest event per pointerId.
// 对于指针事件,每个 pointerId 可以有一个最新的事件。
// 排队指针
const queuedPointers: Map<number, QueuedReplayableEvent> = new Map();
// 排队指针(捕获)
const queuedPointerCaptures: Map<number, QueuedReplayableEvent> = new Map();
// We could consider replaying selectionchange and touchmoves too.
// 我们也可以考虑重新播放 selectionchange 和 touchmove 事件。
// 排队更改事件目标
const queuedChangeEventTargets: Array<EventTarget> = [];
2. 排队的显式水化目标
备注
该部分在源码中的 96 - 127 行
// 排队的显式水化目标
const queuedExplicitHydrationTargets: Array<QueuedHydrationTarget> = [];
// 可离散重放事件
const discreteReplayableEvents: Array<DOMEventName> = [
'mousedown',
'mouseup',
'touchcancel',
'touchend',
'touchstart',
'auxclick',
'dblclick',
'pointercancel',
'pointerdown',
'pointerup',
'dragend',
'dragstart',
'drop',
'compositionend',
'compositionstart',
'keydown',
'keypress',
'keyup',
'input',
// 有意使用驼峰命名
'textInput', // Intentionally camelCase
'copy',
'cut',
'paste',
'click',
'change',
'contextmenu',
'reset',
// stopPropagation 会阻止重放机制
// 'submit', // stopPropagation blocks the replay mechanism
];
变量
1. 已安排重播尝试
备注
在源码的第 77 - 83 行
let hasScheduledReplayAttempt = false;
// The last of each continuous event type. We only need to replay the last one
// if the last target was dehydrated.
// 每种连续事件类型的最后一次。如果最后的目标是脱水状态的话,我们只需要重放最后一次
// 排队焦点
let queuedFocus: null | QueuedReplayableEvent = null;
// 排队拖动
let queuedDrag: null | QueuedReplayableEvent = null;
// 排队鼠标
let queuedMouse: null | QueuedReplayableEvent = null;
2. 上次计划重播队列
备注
该段在源码 514 行
let lastScheduledReplayQueue: null | FormReplayingQueue = null;
工具
1. 创建可排队可重放事件
function createQueuedReplayableEvent(
blockedOn: null | Container | ActivityInstance | SuspenseInstance,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
): QueuedReplayableEvent {
return {
blockedOn,
domEventName,
eventSystemFlags,
nativeEvent,
targetContainers: [targetContainer],
};
}
2. 累积或创建连续排队可重放事件
备注
attemptContinuousHydration()由 ReactFiberReconciler#attemptContinuousHydration 实现getInstanceFromNode()由 ReactDOMComponentTree 提供
function accumulateOrCreateContinuousQueuedReplayableEvent(
existingQueuedEvent: null | QueuedReplayableEvent,
blockedOn: null | Container | ActivityInstance | SuspenseInstance,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
): QueuedReplayableEvent {
if (
existingQueuedEvent === null ||
existingQueuedEvent.nativeEvent !== nativeEvent
) {
const queuedEvent = createQueuedReplayableEvent(
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
if (blockedOn !== null) {
const fiber = getInstanceFromNode(blockedOn);
if (fiber !== null) {
// Attempt to increase the priority of this target.
// 尝试提高此目标的优先级。
attemptContinuousHydration(fiber);
}
}
return queuedEvent;
}
// If we have already queued this exact event, then it's because
// the different event systems have different DOM event listeners.
// We can accumulate the flags, and the targetContainers, and
// store a single event to be replayed.
//
// 如果我们已经将这个相同的事件排入队列,那是因为不同的事件系统有不同的 DOM 事件
// 监听器。我们可以累积这些标志和 targetContainers,并存储一个单一的事件以便
// 重新触发。
existingQueuedEvent.eventSystemFlags |= eventSystemFlags;
const targetContainers = existingQueuedEvent.targetContainers;
if (
targetContainer !== null &&
targetContainers.indexOf(targetContainer) === -1
) {
targetContainers.push(targetContainer);
}
return existingQueuedEvent;
}
3. 尝试显式水化目标
备注
getNearestMountedFiber()由 ReactFiberTreeReflection#getNearestMountedFiber 实现getSuspenseInstanceFromFiber()由 ReactFiberTreeReflection#getSuspenseInstanceFromFiber 实现attemptHydrationAtCurrentPriority()由 ReactFiberReconciler#attemptHydrationAtCurrentPriority 实现getClosestInstanceFromNode()由 ReactDOMComponentTree 提供attemptHydrationAtPriority()由 ReactDOMUpdatePriority 提供
// Check if this target is unblocked. Returns true if it's unblocked.
// 检查此目标是否未被阻塞。如果未被阻塞,则返回 true。
function attemptExplicitHydrationTarget(
queuedTarget: QueuedHydrationTarget,
): void {
// TODO: This function shares a lot of logic with findInstanceBlockingEvent.
// Try to unify them. It's a bit tricky since it would require two return
// values.
// 待办:这个函数和 findInstanceBlockingEvent 有很多逻辑相似。
// 尝试将它们统一。不过这有点棘手,因为可能需要返回两个值。
const targetInst = getClosestInstanceFromNode(queuedTarget.target);
if (targetInst !== null) {
const nearestMounted = getNearestMountedFiber(targetInst);
if (nearestMounted !== null) {
const tag = nearestMounted.tag;
if (tag === SuspenseComponent) {
const instance = getSuspenseInstanceFromFiber(nearestMounted);
if (instance !== null) {
// We're blocked on hydrating this boundary.
// Increase its priority.
// 我们在为这个边界提供数据时被阻塞了。提高它的优先级。
queuedTarget.blockedOn = instance;
attemptHydrationAtPriority(queuedTarget.priority, () => {
attemptHydrationAtCurrentPriority(nearestMounted);
});
return;
}
} else if (tag === ActivityComponent) {
const instance = getActivityInstanceFromFiber(nearestMounted);
if (instance !== null) {
// We're blocked on hydrating this boundary.
// Increase its priority.
// 我们在处理这个边界的水合时被阻塞了。提高它的优先级。
queuedTarget.blockedOn = instance;
attemptHydrationAtPriority(queuedTarget.priority, () => {
attemptHydrationAtCurrentPriority(nearestMounted);
});
return;
}
} else if (tag === HostRoot) {
const root: FiberRoot = nearestMounted.stateNode;
if (isRootDehydrated(root)) {
queuedTarget.blockedOn = getContainerFromFiber(nearestMounted);
// We don't currently have a way to increase the priority of
// a root other than sync.
// 我们目前没有办法提高根节点的优先级,除非是同步。
return;
}
}
}
}
queuedTarget.blockedOn = null;
}
4. 尝试重放连续队列事件
备注
setReplayingEvent()由 CurrentReplayingEvent#setReplayingEvent 实现resetReplayingEvent()由 CurrentReplayingEvent#resetReplayingEvent 实现attemptContinuousHydration()由 ReactFiberReconciler#attemptContinuousHydration 实现findInstanceBlockingEvent()由 ReactDOMEventListener 提供getInstanceFromNode()由 ReactDOMComponentTree 提供
function attemptReplayContinuousQueuedEvent(
queuedEvent: QueuedReplayableEvent,
): boolean {
if (queuedEvent.blockedOn !== null) {
return false;
}
const targetContainers = queuedEvent.targetContainers;
while (targetContainers.length > 0) {
const nextBlockedOn = findInstanceBlockingEvent(queuedEvent.nativeEvent);
if (nextBlockedOn === null) {
const nativeEvent = queuedEvent.nativeEvent;
const nativeEventClone = new nativeEvent.constructor(
nativeEvent.type,
(nativeEvent: any),
);
setReplayingEvent(nativeEventClone);
nativeEvent.target.dispatchEvent(nativeEventClone);
resetReplayingEvent();
} else {
// We're still blocked. Try again later.
// 我们仍然被阻止。请稍后再试。
const fiber = getInstanceFromNode(nextBlockedOn);
if (fiber !== null) {
attemptContinuousHydration(fiber);
}
queuedEvent.blockedOn = nextBlockedOn;
return false;
}
// This target container was successfully dispatched. Try the next.
// 该目标容器已成功派送。尝试下一个。
targetContainers.shift();
}
return true;
}
5. 尝试在地图中连续排队重放事件
function attemptReplayContinuousQueuedEventInMap(
queuedEvent: QueuedReplayableEvent,
key: number,
map: Map<number, QueuedReplayableEvent>,
): void {
if (attemptReplayContinuousQueuedEvent(queuedEvent)) {
map.delete(key);
}
}
6. 重放更改事件
function replayChangeEvent(target: EventTarget): void {
// Dispatch a fake "change" event for the input.
// 为输入框触发一个假的“change”事件。
const element: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =
(target: any);
if (element.nodeName === 'INPUT') {
if (element.type === 'checkbox' || element.type === 'radio') {
// Checkboxes always fire a click event regardless of how the change was made.
// 无论更改是如何进行的,复选框总会触发点击事件。
const EventCtr =
typeof PointerEvent === 'function' ? PointerEvent : Event;
target.dispatchEvent(new EventCtr('click', {bubbles: true}));
// For checkboxes the input event uses the Event constructor instead of InputEvent.
// 对于复选框,input 事件使用的是 Event 构造函数而不是 InputEvent。
target.dispatchEvent(new Event('input', {bubbles: true}));
} else {
if (typeof InputEvent === 'function') {
target.dispatchEvent(new InputEvent('input', {bubbles: true}));
}
}
} else if (element.nodeName === 'TEXTAREA') {
if (typeof InputEvent === 'function') {
target.dispatchEvent(new InputEvent('input', {bubbles: true}));
}
}
target.dispatchEvent(new Event('change', {bubbles: true}));
}
7. 重放未阻止的事件
function replayUnblockedEvents() {
hasScheduledReplayAttempt = false;
// Replay any continuous events.
if (queuedFocus !== null && attemptReplayContinuousQueuedEvent(queuedFocus)) {
queuedFocus = null;
}
if (queuedDrag !== null && attemptReplayContinuousQueuedEvent(queuedDrag)) {
queuedDrag = null;
}
if (queuedMouse !== null && attemptReplayContinuousQueuedEvent(queuedMouse)) {
queuedMouse = null;
}
queuedPointers.forEach(attemptReplayContinuousQueuedEventInMap);
queuedPointerCaptures.forEach(attemptReplayContinuousQueuedEventInMap);
if (enableHydrationChangeEvent) {
for (let i = 0; i < queuedChangeEventTargets.length; i++) {
replayChangeEvent(queuedChangeEventTargets[i]);
}
queuedChangeEventTargets.length = 0;
}
}
8. 如果未被阻塞则安排回调
备注
scheduleCallback()由 scheduleCallback 实现enableHydrationChangeEvent()由 ReactFeatureFlags#enableHydrationChangeEvent 提供
function scheduleCallbackIfUnblocked(
queuedEvent: QueuedReplayableEvent,
unblocked: Container | SuspenseInstance | ActivityInstance,
) {
if (queuedEvent.blockedOn === unblocked) {
queuedEvent.blockedOn = null;
if (!hasScheduledReplayAttempt) {
hasScheduledReplayAttempt = true;
if (!enableHydrationChangeEvent) {
// Schedule a callback to attempt replaying as many events as are
// now unblocked. This first might not actually be unblocked yet.
// We could check it early to avoid scheduling an unnecessary callback.
// 安排一个回调,以尝试重放当前已解锁的尽可能多的事件。这个事件起初可能还未
// 真正解锁。我们可以提前检查它,以避免安排不必要的回调。
scheduleCallback(NormalPriority, replayUnblockedEvents);
}
}
}
}
9. 重放未阻止的表单操作
备注
dispatchReplayedFormAction()由 FormActionEventPlugin#dispatchReplayedForAction 实现findInstanceBlockingTarget()由 ReactDOMEventListener 提供getInstanceFromNode()由 ReactDOMComponentTree 提供
function replayUnblockedFormActions(formReplayingQueue: FormReplayingQueue) {
if (lastScheduledReplayQueue === formReplayingQueue) {
lastScheduledReplayQueue = null;
}
for (let i = 0; i < formReplayingQueue.length; i += 3) {
const form: HTMLFormElement = formReplayingQueue[i];
const submitterOrAction:
| null
| HTMLInputElement
| HTMLButtonElement
| FormAction = formReplayingQueue[i + 1];
const formData: FormData = formReplayingQueue[i + 2];
if (typeof submitterOrAction !== 'function') {
// This action is not hydrated yet. This might be because it's blocked on
// a different React instance or higher up our tree.
// 此操作尚未被水化。这可能是因为它被阻塞在另一个 React 实例上或在我们树的
// 更高层级。
const blockedOn = findInstanceBlockingTarget(submitterOrAction || form);
if (blockedOn === null) {
// We're not blocked but we don't have an action. This must mean that
// this is in another React instance. We'll just skip past it.
// 我们没有被阻塞,但也没有可执行的操作。这一定意味着它在另一个 React 实例中。
// 我们就直接跳过它吧。
continue;
} else {
// We're blocked on something in this React instance. We'll retry later.
// 我们在这个 React 实例中遇到了一些阻塞。我们稍后会重试。
break;
}
}
const formInst = getInstanceFromNode(form);
if (formInst !== null) {
// This is part of our instance.
// We're ready to replay this. Let's delete it from the queue.
// 这是我们实例的一部分。
// 我们已经准备好重放它了。让我们从队列中删除它。
formReplayingQueue.splice(i, 3);
i -= 3;
dispatchReplayedFormAction(formInst, form, submitterOrAction, formData);
// Continue without incrementing the index.
// 继续而不增加索引。
continue;
}
// This form must've been part of a different React instance.
// If we want to preserve ordering between React instances on the same root
// we'd need some way for the other instance to ping us when it's done.
// We'll just skip this and let the other instance execute it.
// 这个表单一定是属于另一个 React 实例的。如果我们想在同一个根节点的 React 实例
// 之间保持顺序。我们需要某种方式让另一个实例在完成时通知我们。我们就跳过这个,让
// 另一个实例执行它。
}
}
10. 如有需要安排重放队列
备注
scheduleCallback()由 scheduleCallback 实现
function scheduleReplayQueueIfNeeded(formReplayingQueue: FormReplayingQueue) {
// Schedule a callback to execute any unblocked form actions in.
// We only keep track of the last queue which means that if multiple React oscillate
// commits, we could schedule more callbacks than necessary but it's not a big deal
// and we only really except one instance.
// 调度一个回调以执行任何未被阻止的表单操作。我们只跟踪最后一个队列,这意味着如果
// 多个 React 振荡提交,我们可能会调度比必要更多的回调,但这没什么大不了的,而且我们
// 通常只会出现一次实例。
if (lastScheduledReplayQueue !== formReplayingQueue) {
lastScheduledReplayQueue = formReplayingQueue;
scheduleCallback(NormalPriority, () =>
replayUnblockedFormActions(formReplayingQueue),
);
}
}
类型
1. 指针事件类型
// TODO: Upgrade this definition once we're on a newer version of Flow that
// has this definition built-in.
// 待办事项:一旦我们升级到包含此定义的新版本 Flow 后,更新此定义。
// 指针事件类型
type PointerEventType = Event & {
pointerId: number;
relatedTarget: EventTarget | null;
// ...
};
// 排队可重放事件
type QueuedReplayableEvent = {
blockedOn: null | Container | ActivityInstance | SuspenseInstance;
domEventName: DOMEventName;
eventSystemFlags: EventSystemFlags;
nativeEvent: AnyNativeEvent;
targetContainers: Array<EventTarget>;
};
2. 排队的水化目标
备注
该部分在源码中的 91 - 95 行
type QueuedHydrationTarget = {
blockedOn: null | Container | ActivityInstance | SuspenseInstance;
target: Node;
priority: EventPriority;
};
3. 表单操作
备注
该段在源码 510 - 512 行
type FormAction = FormData => void | Promise<void>;
type FormReplayingQueue = Array<any>; // [form, submitter or action, formData...]