mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Convert snapshot phase to depth-first traversal (#20622)
This commit is contained in:
@@ -75,9 +75,10 @@ import {
|
||||
Hydrating,
|
||||
HydratingAndUpdate,
|
||||
Passive,
|
||||
BeforeMutationMask,
|
||||
MutationMask,
|
||||
PassiveMask,
|
||||
LayoutMask,
|
||||
PassiveMask,
|
||||
PassiveUnmountPendingDev,
|
||||
} from './ReactFiberFlags';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
@@ -128,6 +129,8 @@ import {
|
||||
commitHydratedSuspenseInstance,
|
||||
clearContainer,
|
||||
prepareScopeUpdate,
|
||||
prepareForCommit,
|
||||
beforeActiveInstanceBlur,
|
||||
} from './ReactFiberHostConfig';
|
||||
import {
|
||||
captureCommitPhaseError,
|
||||
@@ -144,6 +147,7 @@ import {
|
||||
Passive as HookPassive,
|
||||
} from './ReactHookEffectTags';
|
||||
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
|
||||
import {doesFiberContain} from './ReactFiberTreeReflection';
|
||||
|
||||
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
|
||||
if (__DEV__) {
|
||||
@@ -259,18 +263,114 @@ function safelyCallDestroy(current: Fiber, destroy: () => void) {
|
||||
}
|
||||
}
|
||||
|
||||
function commitBeforeMutationLifeCycles(
|
||||
current: Fiber | null,
|
||||
finishedWork: Fiber,
|
||||
): void {
|
||||
switch (finishedWork.tag) {
|
||||
case FunctionComponent:
|
||||
case ForwardRef:
|
||||
case SimpleMemoComponent: {
|
||||
let focusedInstanceHandle: null | Fiber = null;
|
||||
let shouldFireAfterActiveInstanceBlur: boolean = false;
|
||||
|
||||
export function commitBeforeMutationEffects(
|
||||
root: FiberRoot,
|
||||
firstChild: Fiber,
|
||||
) {
|
||||
focusedInstanceHandle = prepareForCommit(root.containerInfo);
|
||||
|
||||
nextEffect = firstChild;
|
||||
commitBeforeMutationEffects_begin();
|
||||
|
||||
// We no longer need to track the active instance fiber
|
||||
const shouldFire = shouldFireAfterActiveInstanceBlur;
|
||||
shouldFireAfterActiveInstanceBlur = false;
|
||||
focusedInstanceHandle = null;
|
||||
|
||||
return shouldFire;
|
||||
}
|
||||
|
||||
function commitBeforeMutationEffects_begin() {
|
||||
while (nextEffect !== null) {
|
||||
const fiber = nextEffect;
|
||||
|
||||
// TODO: Should wrap this in flags check, too, as optimization
|
||||
const deletions = fiber.deletions;
|
||||
if (deletions !== null) {
|
||||
for (let i = 0; i < deletions.length; i++) {
|
||||
const deletion = deletions[i];
|
||||
commitBeforeMutationEffectsDeletion(deletion);
|
||||
}
|
||||
}
|
||||
|
||||
const child = fiber.child;
|
||||
if (
|
||||
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
|
||||
child !== null
|
||||
) {
|
||||
ensureCorrectReturnPointer(child, fiber);
|
||||
nextEffect = child;
|
||||
} else {
|
||||
commitBeforeMutationEffects_complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function commitBeforeMutationEffects_complete() {
|
||||
while (nextEffect !== null) {
|
||||
const fiber = nextEffect;
|
||||
if (__DEV__) {
|
||||
setCurrentDebugFiberInDEV(fiber);
|
||||
invokeGuardedCallback(
|
||||
null,
|
||||
commitBeforeMutationEffectsOnFiber,
|
||||
null,
|
||||
fiber,
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
resetCurrentDebugFiberInDEV();
|
||||
} else {
|
||||
try {
|
||||
commitBeforeMutationEffectsOnFiber(fiber);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
}
|
||||
|
||||
const sibling = fiber.sibling;
|
||||
if (sibling !== null) {
|
||||
ensureCorrectReturnPointer(sibling, fiber.return);
|
||||
nextEffect = sibling;
|
||||
return;
|
||||
}
|
||||
case ClassComponent: {
|
||||
if (finishedWork.flags & Snapshot) {
|
||||
|
||||
nextEffect = fiber.return;
|
||||
}
|
||||
}
|
||||
|
||||
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
|
||||
const current = finishedWork.alternate;
|
||||
const flags = finishedWork.flags;
|
||||
|
||||
if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
|
||||
// Check to see if the focused element was inside of a hidden (Suspense) subtree.
|
||||
// TODO: Move this out of the hot path using a dedicated effect tag.
|
||||
if (
|
||||
finishedWork.tag === SuspenseComponent &&
|
||||
isSuspenseBoundaryBeingHidden(current, finishedWork) &&
|
||||
doesFiberContain(finishedWork, focusedInstanceHandle)
|
||||
) {
|
||||
shouldFireAfterActiveInstanceBlur = true;
|
||||
beforeActiveInstanceBlur(finishedWork);
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & Snapshot) !== NoFlags) {
|
||||
setCurrentDebugFiberInDEV(finishedWork);
|
||||
|
||||
switch (finishedWork.tag) {
|
||||
case FunctionComponent:
|
||||
case ForwardRef:
|
||||
case SimpleMemoComponent: {
|
||||
break;
|
||||
}
|
||||
case ClassComponent: {
|
||||
if (current !== null) {
|
||||
const prevProps = current.memoizedProps;
|
||||
const prevState = current.memoizedState;
|
||||
@@ -324,30 +424,43 @@ function commitBeforeMutationLifeCycles(
|
||||
}
|
||||
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case HostRoot: {
|
||||
if (supportsMutation) {
|
||||
if (finishedWork.flags & Snapshot) {
|
||||
case HostRoot: {
|
||||
if (supportsMutation) {
|
||||
const root = finishedWork.stateNode;
|
||||
clearContainer(root.containerInfo);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HostComponent:
|
||||
case HostText:
|
||||
case HostPortal:
|
||||
case IncompleteClassComponent:
|
||||
// Nothing to do for these component types
|
||||
break;
|
||||
default: {
|
||||
invariant(
|
||||
false,
|
||||
'This unit of work tag should not have side-effects. This error is ' +
|
||||
'likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case HostComponent:
|
||||
case HostText:
|
||||
case HostPortal:
|
||||
case IncompleteClassComponent:
|
||||
// Nothing to do for these component types
|
||||
return;
|
||||
|
||||
resetCurrentDebugFiberInDEV();
|
||||
}
|
||||
}
|
||||
|
||||
function commitBeforeMutationEffectsDeletion(deletion: Fiber) {
|
||||
// TODO (effects) It would be nice to avoid calling doesFiberContain()
|
||||
// Maybe we can repurpose one of the subtreeFlags positions for this instead?
|
||||
// Use it to store which part of the tree the focused instance is in?
|
||||
// This assumes we can safely determine that instance during the "render" phase.
|
||||
if (doesFiberContain(deletion, ((focusedInstanceHandle: any): Fiber))) {
|
||||
shouldFireAfterActiveInstanceBlur = true;
|
||||
beforeActiveInstanceBlur(deletion);
|
||||
}
|
||||
invariant(
|
||||
false,
|
||||
'This unit of work tag should not have side-effects. This error is ' +
|
||||
'likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
function commitHookEffectListUnmount(flags: HookFlags, finishedWork: Fiber) {
|
||||
@@ -2353,7 +2466,6 @@ function ensureCorrectReturnPointer(fiber, expectedReturnFiber) {
|
||||
}
|
||||
|
||||
export {
|
||||
commitBeforeMutationLifeCycles,
|
||||
commitResetTextContent,
|
||||
commitPlacement,
|
||||
commitDeletion,
|
||||
|
||||
@@ -60,6 +60,9 @@ export const MountPassiveDev = /* */ 0b10000000000000000000;
|
||||
// don't contain effects, by checking subtreeFlags.
|
||||
|
||||
export const BeforeMutationMask =
|
||||
// TODO: Remove Update flag from before mutation phase by re-landing Visiblity
|
||||
// flag logic (see #20043)
|
||||
Update |
|
||||
Snapshot |
|
||||
(enableCreateEventHandleAPI
|
||||
? // createEventHandle needs to visit deleted and hidden trees to
|
||||
|
||||
@@ -83,13 +83,11 @@ import * as Scheduler from 'scheduler';
|
||||
import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
|
||||
|
||||
import {
|
||||
prepareForCommit,
|
||||
resetAfterCommit,
|
||||
scheduleTimeout,
|
||||
cancelTimeout,
|
||||
noTimeout,
|
||||
warnsIfNotActing,
|
||||
beforeActiveInstanceBlur,
|
||||
afterActiveInstanceBlur,
|
||||
clearContainer,
|
||||
} from './ReactFiberHostConfig';
|
||||
@@ -122,9 +120,7 @@ import {
|
||||
NoFlags,
|
||||
PerformedWork,
|
||||
Placement,
|
||||
Deletion,
|
||||
ChildDeletion,
|
||||
Snapshot,
|
||||
Passive,
|
||||
PassiveStatic,
|
||||
Incomplete,
|
||||
@@ -181,11 +177,10 @@ import {
|
||||
createClassErrorUpdate,
|
||||
} from './ReactFiberThrow.new';
|
||||
import {
|
||||
commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
|
||||
commitBeforeMutationEffects,
|
||||
commitLayoutEffects,
|
||||
commitMutationEffects,
|
||||
commitPassiveEffectDurations,
|
||||
isSuspenseBoundaryBeingHidden,
|
||||
commitPassiveMountEffects,
|
||||
commitPassiveUnmountEffects,
|
||||
detachFiberAfterEffects,
|
||||
@@ -232,7 +227,6 @@ import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
|
||||
|
||||
// Used by `act`
|
||||
import enqueueTask from 'shared/enqueueTask';
|
||||
import {doesFiberContain} from './ReactFiberTreeReflection';
|
||||
|
||||
const ceil = Math.ceil;
|
||||
|
||||
@@ -363,9 +357,6 @@ let currentEventPendingLanes: Lanes = NoLanes;
|
||||
// We warn about state updates for unmounted components differently in this case.
|
||||
let isFlushingPassiveEffects = false;
|
||||
|
||||
let focusedInstanceHandle: null | Fiber = null;
|
||||
let shouldFireAfterActiveInstanceBlur: boolean = false;
|
||||
|
||||
export function getWorkInProgressRoot(): FiberRoot | null {
|
||||
return workInProgressRoot;
|
||||
}
|
||||
@@ -1941,6 +1932,24 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
firstEffect = finishedWork.firstEffect;
|
||||
}
|
||||
|
||||
// If there are pending passive effects, schedule a callback to process them.
|
||||
// Do this as early as possible, so it is queued before anything else that
|
||||
// might get scheduled in the commit phase. (See #16714.)
|
||||
// TODO: Delete all other places that schedule the passive effect callback
|
||||
// They're redundant.
|
||||
if (
|
||||
(finishedWork.subtreeFlags & Passive) !== NoFlags ||
|
||||
(finishedWork.flags & Passive) !== NoFlags
|
||||
) {
|
||||
if (!rootDoesHavePassiveEffects) {
|
||||
rootDoesHavePassiveEffects = true;
|
||||
scheduleCallback(NormalSchedulerPriority, () => {
|
||||
flushPassiveEffects();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (firstEffect !== null) {
|
||||
let previousLanePriority;
|
||||
if (decoupleUpdatePriorityFromScheduler) {
|
||||
@@ -1962,32 +1971,10 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
// The first phase a "before mutation" phase. We use this phase to read the
|
||||
// state of the host tree right before we mutate it. This is where
|
||||
// getSnapshotBeforeUpdate is called.
|
||||
focusedInstanceHandle = prepareForCommit(root.containerInfo);
|
||||
shouldFireAfterActiveInstanceBlur = false;
|
||||
|
||||
nextEffect = firstEffect;
|
||||
do {
|
||||
if (__DEV__) {
|
||||
invokeGuardedCallback(null, commitBeforeMutationEffects, null);
|
||||
if (hasCaughtError()) {
|
||||
invariant(nextEffect !== null, 'Should be working on an effect.');
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(nextEffect, error);
|
||||
nextEffect = nextEffect.nextEffect;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
commitBeforeMutationEffects();
|
||||
} catch (error) {
|
||||
invariant(nextEffect !== null, 'Should be working on an effect.');
|
||||
captureCommitPhaseError(nextEffect, error);
|
||||
nextEffect = nextEffect.nextEffect;
|
||||
}
|
||||
}
|
||||
} while (nextEffect !== null);
|
||||
|
||||
// We no longer need to track the active instance fiber
|
||||
focusedInstanceHandle = null;
|
||||
const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
|
||||
root,
|
||||
finishedWork,
|
||||
);
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Mark the current commit time to be shared by all Profilers in this
|
||||
@@ -2204,52 +2191,6 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function commitBeforeMutationEffects() {
|
||||
while (nextEffect !== null) {
|
||||
const current = nextEffect.alternate;
|
||||
|
||||
if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
|
||||
if ((nextEffect.flags & Deletion) !== NoFlags) {
|
||||
if (doesFiberContain(nextEffect, focusedInstanceHandle)) {
|
||||
shouldFireAfterActiveInstanceBlur = true;
|
||||
beforeActiveInstanceBlur(nextEffect);
|
||||
}
|
||||
} else {
|
||||
// TODO: Move this out of the hot path using a dedicated effect tag.
|
||||
if (
|
||||
nextEffect.tag === SuspenseComponent &&
|
||||
isSuspenseBoundaryBeingHidden(current, nextEffect) &&
|
||||
doesFiberContain(nextEffect, focusedInstanceHandle)
|
||||
) {
|
||||
shouldFireAfterActiveInstanceBlur = true;
|
||||
beforeActiveInstanceBlur(nextEffect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const flags = nextEffect.flags;
|
||||
if ((flags & Snapshot) !== NoFlags) {
|
||||
setCurrentDebugFiberInDEV(nextEffect);
|
||||
|
||||
commitBeforeMutationEffectOnFiber(current, nextEffect);
|
||||
|
||||
resetCurrentDebugFiberInDEV();
|
||||
}
|
||||
if ((flags & Passive) !== NoFlags) {
|
||||
// If there are passive effects, schedule a callback to flush at
|
||||
// the earliest opportunity.
|
||||
if (!rootDoesHavePassiveEffects) {
|
||||
rootDoesHavePassiveEffects = true;
|
||||
scheduleCallback(NormalSchedulerPriority, () => {
|
||||
flushPassiveEffects();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
nextEffect = nextEffect.nextEffect;
|
||||
}
|
||||
}
|
||||
|
||||
export function flushPassiveEffects(): boolean {
|
||||
// Returns whether passive effects were flushed.
|
||||
if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
|
||||
|
||||
Reference in New Issue
Block a user