From d45b066bcaf4f73b42c14c971b153b7bf25d309a Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 22 Jul 2020 15:54:20 -0400 Subject: [PATCH] Effects list refactor continued: passive effects traversal * Adds new Passive subtree tag value. * Bubbles passive flag to ancestors in the case of an unmount. * Adds recursive traversal for passive effects (mounts and unmounts). * Removes pendingPassiveHookEffectsMount and pendingPassiveHookEffectsUnmount arrays from work loop. * Re-adds sibling and child pointer detaching (temporarily removed in previous PR). * Addresses some minor TODO comments left over from previous PRs. --- .../src/ReactFiberCommitWork.new.js | 72 +-- .../src/ReactFiberHydrationContext.new.js | 2 +- .../src/ReactFiberWorkLoop.new.js | 464 +++++++++++------- .../src/ReactSideEffectTags.js | 1 + .../react-reconciler/src/ReactSubtreeTags.js | 9 +- 5 files changed, 348 insertions(+), 200 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index d854ae52aa25a..b0dbb7585a8e3 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -68,6 +68,8 @@ import { Placement, Snapshot, Update, + Passive, + PassiveUnmountPendingDev, } from './ReactSideEffectTags'; import getComponentName from 'shared/getComponentName'; import invariant from 'shared/invariant'; @@ -115,9 +117,8 @@ import { captureCommitPhaseError, resolveRetryWakeable, markCommitTimeOfFallback, - enqueuePendingPassiveHookEffectMount, - enqueuePendingPassiveHookEffectUnmount, enqueuePendingPassiveProfilerEffect, + schedulePassiveEffectCallback, } from './ReactFiberWorkLoop.new'; import { NoEffect as NoHookEffect, @@ -130,6 +131,10 @@ import { updateDeprecatedEventListeners, unmountDeprecatedResponderListeners, } from './ReactFiberDeprecatedEvents.new'; +import { + NoEffect as NoSubtreeTag, + Passive as PassiveSubtreeTag, +} from './ReactSubtreeTags'; let didWarnAboutUndefinedSnapshotBeforeUpdate: Set | null = null; if (__DEV__) { @@ -381,26 +386,6 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) { } } -function schedulePassiveEffects(finishedWork: Fiber) { - const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any); - const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; - if (lastEffect !== null) { - const firstEffect = lastEffect.next; - let effect = firstEffect; - do { - const {next, tag} = effect; - if ( - (tag & HookPassive) !== NoHookEffect && - (tag & HookHasEffect) !== NoHookEffect - ) { - enqueuePendingPassiveHookEffectUnmount(finishedWork, effect); - enqueuePendingPassiveHookEffectMount(finishedWork, effect); - } - effect = next; - } while (effect !== firstEffect); - } -} - export function commitPassiveEffectDurations( finishedRoot: FiberRoot, finishedWork: Fiber, @@ -486,7 +471,9 @@ function commitLifeCycles( commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork); } - schedulePassiveEffects(finishedWork); + if ((finishedWork.subtreeTag & PassiveSubtreeTag) !== NoSubtreeTag) { + schedulePassiveEffectCallback(); + } return; } case ClassComponent: { @@ -892,7 +879,35 @@ function commitUnmount( const {destroy, tag} = effect; if (destroy !== undefined) { if ((tag & HookPassive) !== NoHookEffect) { - enqueuePendingPassiveHookEffectUnmount(current, effect); + effect.tag |= HookHasEffect; + + // subtreeTags bubble in resetChildLanes which doens't get called for unmounted subtrees. + // So in the case of unmounts, we need to bubble passive effects explicitly. + let ancestor = current.return; + while (ancestor !== null) { + ancestor.subtreeTag |= PassiveSubtreeTag; + const alternate = ancestor.alternate; + if (alternate !== null) { + alternate.subtreeTag |= PassiveSubtreeTag; + } + + ancestor = ancestor.return; + } + + current.effectTag |= Passive; + + if (__DEV__) { + // This flag is used to avoid warning about an update to an unmounted component + // if the component has a passive unmount scheduled. + // Presumably the listener would be cleaned up by that unmount. + current.effectTag |= PassiveUnmountPendingDev; + const alternate = current.alternate; + if (alternate !== null) { + alternate.effectTag |= PassiveUnmountPendingDev; + } + } + + schedulePassiveEffectCallback(); } else { if ( enableProfilerTimer && @@ -1013,8 +1028,11 @@ function commitNestedUnmounts( } function detachFiberMutation(fiber: Fiber) { - // Cut off the return pointers to disconnect it from the tree. Ideally, we - // should clear the child pointer of the parent alternate to let this + // Cut off the return pointers to disconnect it from the tree. + // Note that we can't clear child or sibling pointers yet, + // because they may be required for passive effects. + // These pointers will be cleared in a separate pass. + // Ideally, we should clear the child pointer of the parent alternate to let this // get GC:ed but we don't know which for sure which parent is the current // one so we'll settle for GC:ing the subtree of this child. This child // itself will be GC:ed when the parent updates the next time. @@ -1023,7 +1041,6 @@ function detachFiberMutation(fiber: Fiber) { // traversal in a later effect. See PR #16820. We now clear the sibling // field after effects, see: detachFiberAfterEffects. fiber.alternate = null; - fiber.child = null; fiber.dependencies = null; fiber.firstEffect = null; fiber.lastEffect = null; @@ -1032,7 +1049,6 @@ function detachFiberMutation(fiber: Fiber) { fiber.pendingProps = null; fiber.return = null; fiber.stateNode = null; - fiber.updateQueue = null; if (__DEV__) { fiber._debugOwner = null; } diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js index 5071f76fb5fe4..cc9965e2ea81a 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js @@ -124,7 +124,7 @@ function deleteHydratableInstance( const childToDelete = createFiberFromHostInstanceForDeletion(); childToDelete.stateNode = instance; childToDelete.return = returnFiber; - childToDelete.effectTag = Deletion; + const deletions = returnFiber.deletions; if (deletions === null) { returnFiber.deletions = [childToDelete]; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 0540d53df5534..ca0e0d1316951 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -16,6 +16,7 @@ import type {SuspenseConfig} from './ReactFiberSuspenseConfig'; import type {SuspenseState} from './ReactFiberSuspenseComponent.new'; import type {Effect as HookEffect} from './ReactFiberHooks.new'; import type {StackCursor} from './ReactFiberStack.new'; +import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new'; import { warnAboutDeprecatedLifecycles, @@ -49,6 +50,11 @@ import { flushSyncCallbackQueue, scheduleSyncCallback, } from './SchedulerWithReactIntegration.new'; +import { + NoEffect as NoHookEffect, + HasEffect as HookHasEffect, + Passive as HookPassive, +} from './ReactHookEffectTags'; import { logCommitStarted, logCommitStopped, @@ -134,12 +140,14 @@ import { BeforeMutationMask, MutationMask, LayoutMask, + PassiveMask, } from './ReactSideEffectTags'; import { NoEffect as NoSubtreeTag, - BeforeMutation, - Mutation, - Layout, + BeforeMutation as BeforeMutationSubtreeTag, + Mutation as MutationSubtreeTag, + Layout as LayoutSubtreeTag, + Passive as PassiveSubtreeTag, } from './ReactSubtreeTags'; import { NoLanePriority, @@ -327,8 +335,6 @@ let rootDoesHavePassiveEffects: boolean = false; let rootWithPendingPassiveEffects: FiberRoot | null = null; let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority; let pendingPassiveEffectsLanes: Lanes = NoLanes; -let pendingPassiveHookEffectsMount: Array = []; -let pendingPassiveHookEffectsUnmount: Array = []; let pendingPassiveProfilerEffects: Array = []; let rootsWithPendingDiscreteUpdates: Set | null = null; @@ -1882,13 +1888,16 @@ function resetChildLanes(completedWork: Fiber) { const effectTag = child.effectTag; if ((effectTag & BeforeMutationMask) !== NoEffect) { - subtreeTag |= BeforeMutation; + subtreeTag |= BeforeMutationSubtreeTag; } if ((effectTag & MutationMask) !== NoEffect) { - subtreeTag |= Mutation; + subtreeTag |= MutationSubtreeTag; } if ((effectTag & LayoutMask) !== NoEffect) { - subtreeTag |= Layout; + subtreeTag |= LayoutSubtreeTag; + } + if ((effectTag & PassiveMask) !== NoEffect) { + subtreeTag |= PassiveSubtreeTag; } // When a fiber is cloned, its actualDuration is reset to 0. This value will @@ -1929,13 +1938,16 @@ function resetChildLanes(completedWork: Fiber) { const effectTag = child.effectTag; if ((effectTag & BeforeMutationMask) !== NoEffect) { - subtreeTag |= BeforeMutation; + subtreeTag |= BeforeMutationSubtreeTag; } if ((effectTag & MutationMask) !== NoEffect) { - subtreeTag |= Mutation; + subtreeTag |= MutationSubtreeTag; } if ((effectTag & LayoutMask) !== NoEffect) { - subtreeTag |= Layout; + subtreeTag |= LayoutSubtreeTag; + } + if ((effectTag & PassiveMask) !== NoEffect) { + subtreeTag |= PassiveSubtreeTag; } child = child.sibling; @@ -2170,6 +2182,17 @@ function commitRootImpl(root, renderPriorityLevel) { markLayoutEffectsStopped(); } + // If there are pending passive effects, schedule a callback to process them. + if ((root.current.subtreeTag & PassiveSubtreeTag) !== NoSubtreeTag) { + if (!rootDoesHavePassiveEffects) { + rootDoesHavePassiveEffects = true; + scheduleCallback(NormalSchedulerPriority, () => { + flushPassiveEffects(); + return null; + }); + } + } + // Tell Scheduler to yield at the end of the frame, so the browser has an // opportunity to paint. requestPaint(); @@ -2201,8 +2224,6 @@ function commitRootImpl(root, renderPriorityLevel) { rootWithPendingPassiveEffects = root; pendingPassiveEffectsLanes = lanes; pendingPassiveEffectsRenderPriority = renderPriorityLevel; - } else { - // TODO (effects) Detach sibling pointers for deleted Fibers } // Read this again, since an effect might have updated it @@ -2312,7 +2333,7 @@ function commitBeforeMutationEffects(firstChild: Fiber) { } if (fiber.child !== null) { - const primarySubtreeTag = fiber.subtreeTag & BeforeMutation; + const primarySubtreeTag = fiber.subtreeTag & BeforeMutationSubtreeTag; if (primarySubtreeTag !== NoSubtreeTag) { commitBeforeMutationEffects(fiber.child); } @@ -2396,19 +2417,20 @@ function commitMutationEffects( ) { let fiber = firstChild; while (fiber !== null) { - if (fiber.deletions !== null) { - commitMutationEffectsDeletions( - fiber.deletions, - root, - renderPriorityLevel, - ); + const deletions = fiber.deletions; + if (deletions !== null) { + commitMutationEffectsDeletions(deletions, root, renderPriorityLevel); - // TODO (effects) Don't clear this yet; we may need to cleanup passive effects - fiber.deletions = null; + // If there are no pending passive effects, clear the deletions Array. + const primaryEffectTag = fiber.effectTag & PassiveMask; + const primarySubtreeTag = fiber.subtreeTag & PassiveSubtreeTag; + if (primaryEffectTag === NoEffect && primarySubtreeTag === NoSubtreeTag) { + fiber.deletions = null; + } } if (fiber.child !== null) { - const primarySubtreeTag = fiber.subtreeTag & Mutation; + const primarySubtreeTag = fiber.subtreeTag & MutationSubtreeTag; if (primarySubtreeTag !== NoSubtreeTag) { commitMutationEffects(fiber.child, root, renderPriorityLevel); } @@ -2538,7 +2560,23 @@ function commitMutationEffectsDeletions( captureCommitPhaseError(childToDelete, error); } } - // Don't clear the Deletion effect yet; we also use it to know when we need to detach refs later. + + // If there are no pending passive effects, it's safe to detach remaining pointers now. + const primarySubtreeTag = childToDelete.subtreeTag & PassiveSubtreeTag; + const primaryEffectTag = childToDelete.effectTag & PassiveMask; + if (primarySubtreeTag === NoSubtreeTag && primaryEffectTag === NoEffect) { + detachFiberAfterEffects(childToDelete); + } + } +} + +export function schedulePassiveEffectCallback() { + if (!rootDoesHavePassiveEffects) { + rootDoesHavePassiveEffects = true; + scheduleCallback(NormalSchedulerPriority, () => { + flushPassiveEffects(); + return null; + }); } } @@ -2550,7 +2588,7 @@ function commitLayoutEffects( let fiber = firstChild; while (fiber !== null) { if (fiber.child !== null) { - const primarySubtreeTag = fiber.subtreeTag & Layout; + const primarySubtreeTag = fiber.subtreeTag & LayoutSubtreeTag; if (primarySubtreeTag !== NoSubtreeTag) { commitLayoutEffects(fiber.child, root, committedLanes); } @@ -2645,44 +2683,239 @@ export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void { } } -export function enqueuePendingPassiveHookEffectMount( - fiber: Fiber, - effect: HookEffect, -): void { - pendingPassiveHookEffectsMount.push(effect, fiber); - if (!rootDoesHavePassiveEffects) { - rootDoesHavePassiveEffects = true; - scheduleCallback(NormalSchedulerPriority, () => { - flushPassiveEffects(); - return null; - }); - } +function invokePassiveEffectCreate(effect: HookEffect): void { + const create = effect.create; + effect.destroy = create(); } -export function enqueuePendingPassiveHookEffectUnmount( - fiber: Fiber, - effect: HookEffect, -): void { - pendingPassiveHookEffectsUnmount.push(effect, fiber); - if (__DEV__) { - fiber.effectTag |= PassiveUnmountPendingDev; - const alternate = fiber.alternate; - if (alternate !== null) { - alternate.effectTag |= PassiveUnmountPendingDev; +function flushPassiveMountEffects(firstChild: Fiber): void { + let fiber = firstChild; + while (fiber !== null) { + const didBailout = + fiber.alternate !== null && fiber.alternate.child === fiber.child; + const primarySubtreeTag = fiber.subtreeTag & PassiveSubtreeTag; + + if ( + fiber.child !== null && + !didBailout && + primarySubtreeTag !== NoSubtreeTag + ) { + flushPassiveMountEffects(fiber.child); } + + if ((fiber.effectTag & Update) !== NoEffect) { + switch (fiber.tag) { + case FunctionComponent: + case ForwardRef: + case SimpleMemoComponent: + case Block: { + flushPassiveMountEffectsImpl(fiber); + } + } + } + + fiber = fiber.sibling; } - if (!rootDoesHavePassiveEffects) { - rootDoesHavePassiveEffects = true; - scheduleCallback(NormalSchedulerPriority, () => { - flushPassiveEffects(); - return null; - }); +} + +function flushPassiveMountEffectsImpl(fiber: Fiber): void { + const updateQueue: FunctionComponentUpdateQueue | null = (fiber.updateQueue: any); + const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; + if (lastEffect !== null) { + const firstEffect = lastEffect.next; + let effect = firstEffect; + do { + const {next, tag} = effect; + + if ( + (tag & HookPassive) !== NoHookEffect && + (tag & HookHasEffect) !== NoHookEffect + ) { + if (__DEV__) { + setCurrentDebugFiberInDEV(fiber); + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + fiber.mode & ProfileMode + ) { + startPassiveEffectTimer(); + invokeGuardedCallback( + null, + invokePassiveEffectCreate, + null, + effect, + ); + recordPassiveEffectDuration(fiber); + } else { + invokeGuardedCallback( + null, + invokePassiveEffectCreate, + null, + effect, + ); + } + if (hasCaughtError()) { + invariant(fiber !== null, 'Should be working on an effect.'); + const error = clearCaughtError(); + captureCommitPhaseError(fiber, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + const create = effect.create; + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + fiber.mode & ProfileMode + ) { + try { + startPassiveEffectTimer(); + effect.destroy = create(); + } finally { + recordPassiveEffectDuration(fiber); + } + } else { + effect.destroy = create(); + } + } catch (error) { + invariant(fiber !== null, 'Should be working on an effect.'); + captureCommitPhaseError(fiber, error); + } + } + } + + effect = next; + } while (effect !== firstEffect); } } -function invokePassiveEffectCreate(effect: HookEffect): void { - const create = effect.create; - effect.destroy = create(); +function flushPassiveUnmountEffects(firstChild: Fiber): void { + let fiber = firstChild; + while (fiber !== null) { + const deletions = fiber.deletions; + if (deletions !== null) { + for (let i = 0; i < deletions.length; i++) { + const fiberToDelete = deletions[i]; + // If this fiber (or anything below it) has passive effects then traverse the subtree. + const primaryEffectTag = fiberToDelete.effectTag & PassiveMask; + const primarySubtreeTag = fiberToDelete.subtreeTag & PassiveSubtreeTag; + if ( + primarySubtreeTag !== NoSubtreeTag || + primaryEffectTag !== NoEffect + ) { + flushPassiveUnmountEffects(fiberToDelete); + } + + // Now that passive effects have been processed, it's safe to detach lingering pointers. + detachFiberAfterEffects(fiberToDelete); + } + + // Clear deletions now that passive effects have been procssed. + fiber.deletions = null; + } + + const didBailout = + fiber.alternate !== null && fiber.alternate.child === fiber.child; + if (!didBailout) { + const child = fiber.child; + if (child !== null) { + // If any children have passive effects then traverse the subtree. + // Note that this requires checking subtreeTag of the current Fiber, + // rather than the subtreeTag/effectsTag of the first child, + // since that would not cover passive effects in siblings. + const primarySubtreeTag = fiber.subtreeTag & PassiveSubtreeTag; + if (primarySubtreeTag !== NoSubtreeTag) { + flushPassiveUnmountEffects(child); + } + } + } + + switch (fiber.tag) { + case FunctionComponent: + case ForwardRef: + case SimpleMemoComponent: + case Block: { + const primaryEffectTag = fiber.effectTag & PassiveMask; + if (primaryEffectTag !== NoEffect) { + flushPassiveUnmountEffectsImpl(fiber); + } + } + } + + fiber = fiber.sibling; + } +} + +function flushPassiveUnmountEffectsImpl(fiber: Fiber): void { + const updateQueue: FunctionComponentUpdateQueue | null = (fiber.updateQueue: any); + const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; + if (lastEffect !== null) { + const firstEffect = lastEffect.next; + let effect = firstEffect; + do { + const {next, tag} = effect; + if ( + (tag & HookPassive) !== NoHookEffect && + (tag & HookHasEffect) !== NoHookEffect + ) { + const destroy = effect.destroy; + effect.destroy = undefined; + + if (__DEV__) { + fiber.effectTag &= ~PassiveUnmountPendingDev; + const alternate = fiber.alternate; + if (alternate !== null) { + alternate.effectTag &= ~PassiveUnmountPendingDev; + } + } + + if (typeof destroy === 'function') { + if (__DEV__) { + setCurrentDebugFiberInDEV(fiber); + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + fiber.mode & ProfileMode + ) { + startPassiveEffectTimer(); + invokeGuardedCallback(null, destroy, null); + recordPassiveEffectDuration(fiber); + } else { + invokeGuardedCallback(null, destroy, null); + } + if (hasCaughtError()) { + invariant(fiber !== null, 'Should be working on an effect.'); + const error = clearCaughtError(); + captureCommitPhaseError(fiber, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + fiber.mode & ProfileMode + ) { + try { + startPassiveEffectTimer(); + destroy(); + } finally { + recordPassiveEffectDuration(fiber); + } + } else { + destroy(); + } + } catch (error) { + invariant(fiber !== null, 'Should be working on an effect.'); + captureCommitPhaseError(fiber, error); + } + } + } + } + + effect = next; + } while (effect !== firstEffect); + } } function flushPassiveEffectsImpl() { @@ -2724,117 +2957,8 @@ function flushPassiveEffectsImpl() { // e.g. a destroy function in one component may unintentionally override a ref // value set by a create function in another component. // Layout effects have the same constraint. - - // First pass: Destroy stale passive effects. - const unmountEffects = pendingPassiveHookEffectsUnmount; - pendingPassiveHookEffectsUnmount = []; - for (let i = 0; i < unmountEffects.length; i += 2) { - const effect = ((unmountEffects[i]: any): HookEffect); - const fiber = ((unmountEffects[i + 1]: any): Fiber); - const destroy = effect.destroy; - effect.destroy = undefined; - - if (__DEV__) { - fiber.effectTag &= ~PassiveUnmountPendingDev; - const alternate = fiber.alternate; - if (alternate !== null) { - alternate.effectTag &= ~PassiveUnmountPendingDev; - } - } - - if (typeof destroy === 'function') { - if (__DEV__) { - setCurrentDebugFiberInDEV(fiber); - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - fiber.mode & ProfileMode - ) { - startPassiveEffectTimer(); - invokeGuardedCallback(null, destroy, null); - recordPassiveEffectDuration(fiber); - } else { - invokeGuardedCallback(null, destroy, null); - } - if (hasCaughtError()) { - invariant(fiber !== null, 'Should be working on an effect.'); - const error = clearCaughtError(); - captureCommitPhaseError(fiber, error); - } - resetCurrentDebugFiberInDEV(); - } else { - try { - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - fiber.mode & ProfileMode - ) { - try { - startPassiveEffectTimer(); - destroy(); - } finally { - recordPassiveEffectDuration(fiber); - } - } else { - destroy(); - } - } catch (error) { - invariant(fiber !== null, 'Should be working on an effect.'); - captureCommitPhaseError(fiber, error); - } - } - } - } - // Second pass: Create new passive effects. - const mountEffects = pendingPassiveHookEffectsMount; - pendingPassiveHookEffectsMount = []; - for (let i = 0; i < mountEffects.length; i += 2) { - const effect = ((mountEffects[i]: any): HookEffect); - const fiber = ((mountEffects[i + 1]: any): Fiber); - if (__DEV__) { - setCurrentDebugFiberInDEV(fiber); - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - fiber.mode & ProfileMode - ) { - startPassiveEffectTimer(); - invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect); - recordPassiveEffectDuration(fiber); - } else { - invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect); - } - if (hasCaughtError()) { - invariant(fiber !== null, 'Should be working on an effect.'); - const error = clearCaughtError(); - captureCommitPhaseError(fiber, error); - } - resetCurrentDebugFiberInDEV(); - } else { - try { - const create = effect.create; - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - fiber.mode & ProfileMode - ) { - try { - startPassiveEffectTimer(); - effect.destroy = create(); - } finally { - recordPassiveEffectDuration(fiber); - } - } else { - effect.destroy = create(); - } - } catch (error) { - invariant(fiber !== null, 'Should be working on an effect.'); - captureCommitPhaseError(fiber, error); - } - } - } - - // TODO (effects) Detach sibling pointers for deleted Fibers + flushPassiveUnmountEffects(root.current); + flushPassiveMountEffects(root.current); if (enableProfilerTimer && enableProfilerCommitHooks) { const profilerEffects = pendingPassiveProfilerEffects; @@ -3934,3 +4058,9 @@ export function act(callback: () => Thenable): Thenable { }; } } + +function detachFiberAfterEffects(fiber: Fiber): void { + fiber.child = null; + fiber.sibling = null; + fiber.updateQueue = null; +} diff --git a/packages/react-reconciler/src/ReactSideEffectTags.js b/packages/react-reconciler/src/ReactSideEffectTags.js index 8b63f2f22f8ca..b01aa52aba6e6 100644 --- a/packages/react-reconciler/src/ReactSideEffectTags.js +++ b/packages/react-reconciler/src/ReactSideEffectTags.js @@ -43,3 +43,4 @@ export const ForceUpdateForLegacySuspense = /* */ 0b100000000000000; export const BeforeMutationMask = /* */ 0b000001100001010; export const MutationMask = /* */ 0b000010010011110; export const LayoutMask = /* */ 0b000000010100100; +export const PassiveMask = /* */ 0b000001000000000; diff --git a/packages/react-reconciler/src/ReactSubtreeTags.js b/packages/react-reconciler/src/ReactSubtreeTags.js index 7cc1d7acd090b..536d9e074b448 100644 --- a/packages/react-reconciler/src/ReactSubtreeTags.js +++ b/packages/react-reconciler/src/ReactSubtreeTags.js @@ -9,7 +9,8 @@ export type SubtreeTag = number; -export const NoEffect = /* */ 0b000; -export const BeforeMutation = /* */ 0b001; -export const Mutation = /* */ 0b010; -export const Layout = /* */ 0b100; +export const NoEffect = /* */ 0b0000; +export const BeforeMutation = /* */ 0b0001; +export const Mutation = /* */ 0b0010; +export const Layout = /* */ 0b0100; +export const Passive = /* */ 0b1000;