Land #20595 and #20596 in main fork (#20602)

This commit is contained in:
Andrew Clark
2021-01-19 08:37:51 -08:00
committed by GitHub
parent 33358a852c
commit 98cc91ccce
7 changed files with 570 additions and 522 deletions
@@ -17,7 +17,6 @@ beforeEach(() => {
// Don't feel too guilty if you have to delete this test.
// @gate dfsEffectsRefactor
// @gate new
// @gate __DEV__
test('warns in DEV if return pointer is inconsistent', async () => {
const {useRef, useLayoutEffect} = React;
@@ -282,22 +282,13 @@ function ChildReconciler(shouldTrackSideEffects) {
childToDelete.nextEffect = null;
childToDelete.flags = (childToDelete.flags & StaticMask) | Deletion;
let deletions = returnFiber.deletions;
const deletions = returnFiber.deletions;
if (deletions === null) {
deletions = returnFiber.deletions = [childToDelete];
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
// Stash a reference to the return fiber's deletion array on each of the
// deleted children. This is really weird, but it's a temporary workaround
// while we're still using the effect list to traverse effect fibers. A
// better workaround would be to follow the `.return` pointer in the commit
// phase, but unfortunately we can't assume that `.return` points to the
// correct fiber, even in the commit phase, because `findDOMNode` might
// mutate it.
// TODO: Remove this line.
childToDelete.deletions = deletions;
}
function deleteRemainingChildren(
@@ -2203,14 +2203,13 @@ function updateSuspensePrimaryChildren(
currentFallbackChildFragment.flags =
(currentFallbackChildFragment.flags & StaticMask) | Deletion;
workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChildFragment;
let deletions = workInProgress.deletions;
const deletions = workInProgress.deletions;
if (deletions === null) {
deletions = workInProgress.deletions = [currentFallbackChildFragment];
workInProgress.deletions = [currentFallbackChildFragment];
workInProgress.flags |= ChildDeletion;
} else {
deletions.push(currentFallbackChildFragment);
}
currentFallbackChildFragment.deletions = deletions;
}
workInProgress.child = primaryChildFragment;
@@ -3194,14 +3193,13 @@ function remountFiber(
current.nextEffect = null;
current.flags = (current.flags & StaticMask) | Deletion;
let deletions = returnFiber.deletions;
const deletions = returnFiber.deletions;
if (deletions === null) {
deletions = returnFiber.deletions = [current];
returnFiber.deletions = [current];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(current);
}
current.deletions = deletions;
newWorkInProgress.flags |= Placement;
@@ -66,11 +66,18 @@ import {
NoFlags,
ContentReset,
Placement,
PlacementAndUpdate,
ChildDeletion,
Snapshot,
Update,
Callback,
Ref,
Hydrating,
HydratingAndUpdate,
Passive,
MutationMask,
PassiveMask,
LayoutMask,
PassiveUnmountPendingDev,
} from './ReactFiberFlags';
import getComponentName from 'shared/getComponentName';
@@ -490,326 +497,341 @@ export function commitPassiveEffectDurations(
}
}
function commitLifeCycles(
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
// At this point layout effects have already been destroyed (during mutation phase).
// This is done to prevent sibling component effects from interfering with each other,
// e.g. a destroy function in one component should never override a ref set
// by a create function in another component during the same commit.
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
}
schedulePassiveEffects(finishedWork);
return;
}
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.flags & Update) {
if (current === null) {
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'componentDidMount. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'componentDidMount. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
instance.componentDidMount();
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
instance.componentDidMount();
if ((finishedWork.flags & (Update | Callback)) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
// At this point layout effects have already been destroyed (during mutation phase).
// This is done to prevent sibling component effects from interfering with each other,
// e.g. a destroy function in one component should never override a ref set
// by a create function in another component during the same commit.
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(finishedWork.type, current.memoizedProps);
const prevState = current.memoizedState;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'componentDidUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'componentDidUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
}
schedulePassiveEffects(finishedWork);
break;
}
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.flags & Update) {
if (current === null) {
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'componentDidMount. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'componentDidMount. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
instance.componentDidMount();
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
instance.componentDidMount();
}
} else {
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(finishedWork.type, current.memoizedProps);
const prevState = current.memoizedState;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'componentDidUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'componentDidUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
}
}
}
// TODO: I think this is now always non-null by the time it reaches the
// commit phase. Consider removing the type check.
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
if (updateQueue !== null) {
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'processing the update queue. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'processing the update queue. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
}
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
commitUpdateQueue(finishedWork, updateQueue, instance);
}
return;
}
case HostRoot: {
// TODO: I think this is now always non-null by the time it reaches the
// commit phase. Consider removing the type check.
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
if (updateQueue !== null) {
let instance = null;
if (finishedWork.child !== null) {
switch (finishedWork.child.tag) {
case HostComponent:
instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
instance = finishedWork.child.stateNode;
break;
// TODO: I think this is now always non-null by the time it reaches the
// commit phase. Consider removing the type check.
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
if (updateQueue !== null) {
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'processing the update queue. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'processing the update queue. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
}
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
commitUpdateQueue(finishedWork, updateQueue, instance);
}
commitUpdateQueue(finishedWork, updateQueue, instance);
break;
}
return;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
if (current === null && finishedWork.flags & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
commitMount(instance, type, props, finishedWork);
}
return;
}
case HostText: {
// We have no life-cycles associated with text.
return;
}
case HostPortal: {
// We have no life-cycles associated with portals.
return;
}
case Profiler: {
if (enableProfilerTimer) {
const {onCommit, onRender} = finishedWork.memoizedProps;
const {effectDuration} = finishedWork.stateNode;
const commitTime = getCommitTime();
let phase = current === null ? 'mount' : 'update';
if (enableProfilerNestedUpdatePhase) {
if (isCurrentUpdateNested()) {
phase = 'nested-update';
case HostRoot: {
// TODO: I think this is now always non-null by the time it reaches the
// commit phase. Consider removing the type check.
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
if (updateQueue !== null) {
let instance = null;
if (finishedWork.child !== null) {
switch (finishedWork.child.tag) {
case HostComponent:
instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
instance = finishedWork.child.stateNode;
break;
}
}
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
if (current === null && finishedWork.flags & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
commitMount(instance, type, props, finishedWork);
}
if (typeof onRender === 'function') {
if (enableSchedulerTracing) {
onRender(
finishedWork.memoizedProps.id,
phase,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
finishedRoot.memoizedInteractions,
);
} else {
onRender(
finishedWork.memoizedProps.id,
phase,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
);
}
}
break;
}
case HostText: {
// We have no life-cycles associated with text.
break;
}
case HostPortal: {
// We have no life-cycles associated with portals.
break;
}
case Profiler: {
if (enableProfilerTimer) {
const {onCommit, onRender} = finishedWork.memoizedProps;
const {effectDuration} = finishedWork.stateNode;
if (enableProfilerCommitHooks) {
if (typeof onCommit === 'function') {
const commitTime = getCommitTime();
let phase = current === null ? 'mount' : 'update';
if (enableProfilerNestedUpdatePhase) {
if (isCurrentUpdateNested()) {
phase = 'nested-update';
}
}
if (typeof onRender === 'function') {
if (enableSchedulerTracing) {
onCommit(
onRender(
finishedWork.memoizedProps.id,
phase,
effectDuration,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
finishedRoot.memoizedInteractions,
);
} else {
onCommit(
onRender(
finishedWork.memoizedProps.id,
phase,
effectDuration,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
);
}
}
// Schedule a passive effect for this Profiler to call onPostCommit hooks.
// This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
// because the effect is also where times bubble to parent Profilers.
enqueuePendingPassiveProfilerEffect(finishedWork);
if (enableProfilerCommitHooks) {
if (typeof onCommit === 'function') {
if (enableSchedulerTracing) {
onCommit(
finishedWork.memoizedProps.id,
phase,
effectDuration,
commitTime,
finishedRoot.memoizedInteractions,
);
} else {
onCommit(
finishedWork.memoizedProps.id,
phase,
effectDuration,
commitTime,
);
}
}
// Propagate layout effect durations to the next nearest Profiler ancestor.
// Do not reset these values until the next render so DevTools has a chance to read them first.
let parentFiber = finishedWork.return;
while (parentFiber !== null) {
if (parentFiber.tag === Profiler) {
const parentStateNode = parentFiber.stateNode;
parentStateNode.effectDuration += effectDuration;
break;
// Schedule a passive effect for this Profiler to call onPostCommit hooks.
// This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
// because the effect is also where times bubble to parent Profilers.
enqueuePendingPassiveProfilerEffect(finishedWork);
// Propagate layout effect durations to the next nearest Profiler ancestor.
// Do not reset these values until the next render so DevTools has a chance to read them first.
let parentFiber = finishedWork.return;
while (parentFiber !== null) {
if (parentFiber.tag === Profiler) {
const parentStateNode = parentFiber.stateNode;
parentStateNode.effectDuration += effectDuration;
break;
}
parentFiber = parentFiber.return;
}
parentFiber = parentFiber.return;
}
}
break;
}
return;
case SuspenseComponent: {
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
break;
}
case SuspenseListComponent:
case IncompleteClassComponent:
case FundamentalComponent:
case ScopeComponent:
case OffscreenComponent:
case LegacyHiddenComponent:
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.',
);
}
}
if (enableScopeAPI) {
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
if (finishedWork.flags & Ref && finishedWork.tag !== ScopeComponent) {
commitAttachRef(finishedWork);
}
} else {
if (finishedWork.flags & Ref) {
commitAttachRef(finishedWork);
}
case SuspenseComponent: {
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
return;
}
case SuspenseListComponent:
case IncompleteClassComponent:
case FundamentalComponent:
case ScopeComponent:
case OffscreenComponent:
case LegacyHiddenComponent:
return;
}
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 hideOrUnhideAllChildren(finishedWork, isHidden) {
@@ -1823,12 +1845,254 @@ function commitResetTextContent(current: Fiber) {
resetTextContent(current.stateNode);
}
export function commitMutationEffects(
root: FiberRoot,
renderPriorityLevel: ReactPriorityLevel,
firstChild: Fiber,
) {
nextEffect = firstChild;
commitMutationEffects_begin(root, renderPriorityLevel);
}
function commitMutationEffects_begin(
root: FiberRoot,
renderPriorityLevel: ReactPriorityLevel,
) {
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 childToDelete = deletions[i];
if (__DEV__) {
invokeGuardedCallback(
null,
commitDeletion,
null,
root,
childToDelete,
renderPriorityLevel,
);
if (hasCaughtError()) {
const error = clearCaughtError();
captureCommitPhaseError(childToDelete, error);
}
} else {
try {
commitDeletion(root, childToDelete, renderPriorityLevel);
} catch (error) {
captureCommitPhaseError(childToDelete, error);
}
}
}
}
const child = fiber.child;
if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
commitMutationEffects_complete(root, renderPriorityLevel);
}
}
}
function commitMutationEffects_complete(
root: FiberRoot,
renderPriorityLevel: ReactPriorityLevel,
) {
while (nextEffect !== null) {
const fiber = nextEffect;
if (__DEV__) {
setCurrentDebugFiberInDEV(fiber);
invokeGuardedCallback(
null,
commitMutationEffectsOnFiber,
null,
fiber,
root,
renderPriorityLevel,
);
if (hasCaughtError()) {
const error = clearCaughtError();
captureCommitPhaseError(fiber, error);
}
resetCurrentDebugFiberInDEV();
} else {
try {
commitMutationEffectsOnFiber(fiber, root, renderPriorityLevel);
} catch (error) {
captureCommitPhaseError(fiber, error);
}
}
const sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
renderPriorityLevel: ReactPriorityLevel,
) {
const flags = finishedWork.flags;
if (flags & ContentReset) {
commitResetTextContent(finishedWork);
}
if (flags & Ref) {
const current = finishedWork.alternate;
if (current !== null) {
commitDetachRef(current);
}
if (enableScopeAPI) {
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
if (finishedWork.tag === ScopeComponent) {
commitAttachRef(finishedWork);
}
}
}
// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every possible
// bitmap value, we remove the secondary effects from the effect tag and
// switch on that value.
const primaryFlags = flags & (Placement | Update | Hydrating);
outer: switch (primaryFlags) {
case Placement: {
commitPlacement(finishedWork);
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted does
// and isMounted is deprecated anyway so we should be able to kill this.
finishedWork.flags &= ~Placement;
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(finishedWork);
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
finishedWork.flags &= ~Placement;
// Update
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
case Hydrating: {
finishedWork.flags &= ~Hydrating;
break;
}
case HydratingAndUpdate: {
finishedWork.flags &= ~Hydrating;
// Update
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
case Update: {
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
}
}
export function commitLayoutEffects(
finishedWork: Fiber,
root: FiberRoot,
committedLanes: Lanes,
): void {
nextEffect = finishedWork;
commitLayoutEffects_begin(finishedWork, root, committedLanes);
}
function commitLayoutEffects_begin(
subtreeRoot: Fiber,
root: FiberRoot,
committedLanes: Lanes,
) {
while (nextEffect !== null) {
const fiber = nextEffect;
const firstChild = fiber.child;
if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) {
ensureCorrectReturnPointer(firstChild, fiber);
nextEffect = firstChild;
} else {
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
}
}
}
function commitLayoutMountEffects_complete(
subtreeRoot: Fiber,
root: FiberRoot,
committedLanes: Lanes,
) {
while (nextEffect !== null) {
const fiber = nextEffect;
if ((fiber.flags & LayoutMask) !== NoFlags) {
const current = fiber.alternate;
if (__DEV__) {
setCurrentDebugFiberInDEV(fiber);
invokeGuardedCallback(
null,
commitLayoutEffectOnFiber,
null,
root,
current,
fiber,
committedLanes,
);
if (hasCaughtError()) {
const error = clearCaughtError();
captureCommitPhaseError(fiber, error);
}
resetCurrentDebugFiberInDEV();
} else {
try {
commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
} catch (error) {
captureCommitPhaseError(fiber, error);
}
}
}
if (fiber === subtreeRoot) {
nextEffect = null;
return;
}
const sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
export function commitPassiveMountEffects(
root: FiberRoot,
firstChild: Fiber,
finishedWork: Fiber,
): void {
nextEffect = firstChild;
commitPassiveMountEffects_begin(firstChild, root);
nextEffect = finishedWork;
commitPassiveMountEffects_begin(finishedWork, root);
}
function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) {
@@ -2094,7 +2358,6 @@ export {
commitPlacement,
commitDeletion,
commitWork,
commitLifeCycles,
commitAttachRef,
commitDetachRef,
};
@@ -144,14 +144,13 @@ function deleteHydratableInstance(
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
let deletions = returnFiber.deletions;
const deletions = returnFiber.deletions;
if (deletions === null) {
deletions = returnFiber.deletions = [childToDelete];
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
childToDelete.deletions = deletions;
}
function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
@@ -31,7 +31,6 @@ import {
decoupleUpdatePriorityFromScheduler,
enableDebugTracing,
enableSchedulingProfiler,
enableScopeAPI,
disableSchedulerTimeoutInWorkLoop,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -116,7 +115,6 @@ import {
ForwardRef,
MemoComponent,
SimpleMemoComponent,
ScopeComponent,
Profiler,
} from './ReactWorkTags';
import {LegacyRoot} from './ReactRootTags';
@@ -124,20 +122,14 @@ import {
NoFlags,
PerformedWork,
Placement,
Update,
PlacementAndUpdate,
Deletion,
ChildDeletion,
Ref,
ContentReset,
Snapshot,
Callback,
Passive,
PassiveStatic,
Incomplete,
HostEffectMask,
Hydrating,
HydratingAndUpdate,
StaticMask,
} from './ReactFiberFlags';
import {
@@ -190,14 +182,9 @@ import {
} from './ReactFiberThrow.old';
import {
commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
commitLifeCycles as commitLayoutEffectOnFiber,
commitPlacement,
commitWork,
commitDeletion,
commitDetachRef,
commitAttachRef,
commitLayoutEffects,
commitMutationEffects,
commitPassiveEffectDurations,
commitResetTextContent,
isSuspenseBoundaryBeingHidden,
commitPassiveMountEffects,
commitPassiveUnmountEffects,
@@ -2032,32 +2019,7 @@ function commitRootImpl(root, renderPriorityLevel) {
}
// The next phase is the mutation phase, where we mutate the host tree.
nextEffect = firstEffect;
do {
if (__DEV__) {
invokeGuardedCallback(
null,
commitMutationEffects,
null,
root,
renderPriorityLevel,
);
if (hasCaughtError()) {
invariant(nextEffect !== null, 'Should be working on an effect.');
const error = clearCaughtError();
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
} else {
try {
commitMutationEffects(root, renderPriorityLevel);
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
}
} while (nextEffect !== null);
commitMutationEffects(root, renderPriorityLevel, finishedWork);
if (shouldFireAfterActiveInstanceBlur) {
afterActiveInstanceBlur();
@@ -2073,28 +2035,24 @@ function commitRootImpl(root, renderPriorityLevel) {
// The next phase is the layout phase, where we call effects that read
// the host tree after it's been mutated. The idiomatic use case for this is
// layout, but class component lifecycles also fire here for legacy reasons.
nextEffect = firstEffect;
do {
if (__DEV__) {
invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
if (hasCaughtError()) {
invariant(nextEffect !== null, 'Should be working on an effect.');
const error = clearCaughtError();
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
} else {
try {
commitLayoutEffects(root, lanes);
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
if (__DEV__) {
if (enableDebugTracing) {
logLayoutEffectsStarted(lanes);
}
} while (nextEffect !== null);
}
if (enableSchedulingProfiler) {
markLayoutEffectsStarted(lanes);
}
commitLayoutEffects(finishedWork, root, lanes);
if (__DEV__) {
if (enableDebugTracing) {
logLayoutEffectsStopped();
}
}
nextEffect = null;
if (enableSchedulingProfiler) {
markLayoutEffectsStopped();
}
if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
rootCommittingMutationOrLayoutEffects = null;
@@ -2309,166 +2267,6 @@ function commitBeforeMutationEffects() {
}
}
function commitMutationEffects(
root: FiberRoot,
renderPriorityLevel: ReactPriorityLevel,
) {
// TODO: Should probably move the bulk of this function to commitWork.
while (nextEffect !== null) {
setCurrentDebugFiberInDEV(nextEffect);
const flags = nextEffect.flags;
if (flags & ContentReset) {
commitResetTextContent(nextEffect);
}
if (flags & Ref) {
const current = nextEffect.alternate;
if (current !== null) {
commitDetachRef(current);
}
if (enableScopeAPI) {
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
if (nextEffect.tag === ScopeComponent) {
commitAttachRef(nextEffect);
}
}
}
// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every possible
// bitmap value, we remove the secondary effects from the effect tag and
// switch on that value.
const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
outer: switch (primaryFlags) {
case Placement: {
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted does
// and isMounted is deprecated anyway so we should be able to kill this.
nextEffect.flags &= ~Placement;
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
nextEffect.flags &= ~Placement;
// Update
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Hydrating: {
nextEffect.flags &= ~Hydrating;
break;
}
case HydratingAndUpdate: {
nextEffect.flags &= ~Hydrating;
// Update
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Deletion: {
// Reached a deletion effect. Instead of commit this effect like we
// normally do, we're going to use the `deletions` array of the parent.
// However, because the effect list is sorted in depth-first order, we
// can't wait until we reach the parent node, because the child effects
// will have run in the meantime.
//
// So instead, we use a trick where the first time we hit a deletion
// effect, we commit all the deletion effects that belong to that parent.
//
// This is an incremental step away from using the effect list and
// toward a DFS + subtreeFlags traversal.
//
// A reference to the deletion array of the parent is also stored on
// each of the deletions. This is really weird. It would be better to
// follow the `.return` pointer, but unfortunately we can't assume that
// `.return` points to the correct fiber, even in the commit phase,
// because `findDOMNode` might mutate it.
const deletedChild = nextEffect;
const deletions = deletedChild.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const deletion = deletions[i];
// Clear the deletion effect so that we don't delete this node more
// than once.
deletion.flags &= ~Deletion;
deletion.deletions = null;
commitDeletion(root, deletion, renderPriorityLevel);
}
}
break;
}
}
resetCurrentDebugFiberInDEV();
nextEffect = nextEffect.nextEffect;
}
}
function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
if (__DEV__) {
if (enableDebugTracing) {
logLayoutEffectsStarted(committedLanes);
}
}
if (enableSchedulingProfiler) {
markLayoutEffectsStarted(committedLanes);
}
// TODO: Should probably move the bulk of this function to commitWork.
while (nextEffect !== null) {
setCurrentDebugFiberInDEV(nextEffect);
const flags = nextEffect.flags;
if (flags & (Update | Callback)) {
const current = nextEffect.alternate;
commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
}
if (enableScopeAPI) {
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
if (flags & Ref && nextEffect.tag !== ScopeComponent) {
commitAttachRef(nextEffect);
}
} else {
if (flags & Ref) {
commitAttachRef(nextEffect);
}
}
resetCurrentDebugFiberInDEV();
nextEffect = nextEffect.nextEffect;
}
if (__DEV__) {
if (enableDebugTracing) {
logLayoutEffectsStopped();
}
}
if (enableSchedulingProfiler) {
markLayoutEffectsStopped();
}
}
export function flushPassiveEffects(): boolean {
// Returns whether passive effects were flushed.
if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
+1 -1
View File
@@ -47,7 +47,7 @@ const environmentFlags = {
// Turn these flags back on (or delete) once the effect list is removed in
// favor of a depth-first traversal using `subtreeTags`.
dfsEffectsRefactor: __VARIANT__,
dfsEffectsRefactor: true,
enableUseJSStackToTrackPassiveDurations: false,
};