From d6d2baa4b65618e8a3e7bec81761175caa0458bd Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 15 Jan 2021 15:25:24 -0600 Subject: [PATCH] Land #20595 and #20596 in main fork --- .../__tests__/ReactWrongReturnPointer-test.js | 1 - .../src/ReactChildFiber.old.js | 13 +- .../src/ReactFiberBeginWork.old.js | 10 +- .../src/ReactFiberCommitWork.old.js | 761 ++++++++++++------ .../src/ReactFiberHydrationContext.old.js | 5 +- .../src/ReactFiberWorkLoop.old.js | 240 +----- scripts/jest/TestFlags.js | 2 +- 7 files changed, 540 insertions(+), 492 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js b/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js index 72cb43d75386c..9329a2aa34fc4 100644 --- a/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js +++ b/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js @@ -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; diff --git a/packages/react-reconciler/src/ReactChildFiber.old.js b/packages/react-reconciler/src/ReactChildFiber.old.js index f1a52dedeebb0..1f774b26813bb 100644 --- a/packages/react-reconciler/src/ReactChildFiber.old.js +++ b/packages/react-reconciler/src/ReactChildFiber.old.js @@ -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( diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 5a25d0a9cd65f..bf03aeb24012f 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -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; diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index d3bfe78e39479..fdc9868dbe9b5 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -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,95 +497,154 @@ 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(); + 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 { 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__) { + 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 ( - finishedWork.type === finishedWork.elementType && - !didWarnAboutReassigningProps + enableProfilerTimer && + enableProfilerCommitHooks && + finishedWork.mode & ProfileMode ) { - 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', - ); + try { + startLayoutEffectTimer(); + instance.componentDidMount(); + } finally { + recordLayoutEffectDuration(finishedWork); } - 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(); + } else { instance.componentDidMount(); - } finally { - recordLayoutEffectDuration(finishedWork); } } else { - instance.componentDidMount(); + 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, + ); + } } - } 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. + } + + // 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 && @@ -587,7 +653,7 @@ function commitLifeCycles( if (instance.props !== finishedWork.memoizedProps) { console.error( 'Expected %s props to match memoized props before ' + - 'componentDidUpdate. ' + + '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.', @@ -597,7 +663,7 @@ function commitLifeCycles( if (instance.state !== finishedWork.memoizedState) { console.error( 'Expected %s state to match memoized state before ' + - 'componentDidUpdate. ' + + '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.', @@ -606,210 +672,166 @@ function commitLifeCycles( } } } - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - finishedWork.mode & ProfileMode - ) { - try { - startLayoutEffectTimer(); - instance.componentDidUpdate( - prevProps, - prevState, - instance.__reactInternalSnapshotBeforeUpdate, - ); - } finally { - recordLayoutEffectDuration(finishedWork); - } - } else { - instance.componentDidUpdate( - prevProps, - prevState, - instance.__reactInternalSnapshotBeforeUpdate, - ); - } + // 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); } + 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', - ); + 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); } - // 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); + break; } - 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; - } + 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); } - commitUpdateQueue(finishedWork, updateQueue, instance); - } - 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); + 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; - 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'; - } - } + const commitTime = getCommitTime(); - 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, - ); + let phase = current === null ? 'mount' : 'update'; + if (enableProfilerNestedUpdatePhase) { + if (isCurrentUpdateNested()) { + phase = 'nested-update'; + } } - } - if (enableProfilerCommitHooks) { - if (typeof onCommit === 'function') { + 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.', + ); } - case SuspenseComponent: { - commitSuspenseHydrationCallbacks(finishedRoot, finishedWork); - return; + } + + 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 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 commitPassiveMountEffects( +export function commitMutationEffects( root: FiberRoot, + renderPriorityLevel: ReactPriorityLevel, firstChild: Fiber, -): void { +) { nextEffect = firstChild; - commitPassiveMountEffects_begin(firstChild, root); + 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, + finishedWork: Fiber, +): void { + nextEffect = finishedWork; + commitPassiveMountEffects_begin(finishedWork, root); } function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) { @@ -2094,7 +2358,6 @@ export { commitPlacement, commitDeletion, commitWork, - commitLifeCycles, commitAttachRef, commitDetachRef, }; diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js index a1ca81dc502d4..e0ed2901c52d9 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js @@ -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) { diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index caeab7ce67330..7ed5b098982af 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -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) { diff --git a/scripts/jest/TestFlags.js b/scripts/jest/TestFlags.js index d7e82312dd2a5..d4323bde51f95 100644 --- a/scripts/jest/TestFlags.js +++ b/scripts/jest/TestFlags.js @@ -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, };