From 697702bf341dcf63c47f6de9645970d45f6cf9ef Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 13 Jul 2022 11:45:53 -0400 Subject: [PATCH] Use recursion to traverse during "disappear layout" phase This converts the "disappear layout" phase to iterate over its effects recursively instead of iteratively. This makes it easier to track contextual information, like whether a fiber is inside a hidden tree. We already made this change for several other phases, like mutation and layout mount. See 481dece for more context. --- .../src/ReactFiberCommitWork.new.js | 149 +++++++++--------- .../src/ReactFiberCommitWork.old.js | 149 +++++++++--------- 2 files changed, 142 insertions(+), 156 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 13ad7bd12e745..b7240c362dbff 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -2565,13 +2565,8 @@ function commitMutationEffectsOnFiber( if (isHidden) { if (!wasHidden) { if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) { - nextEffect = offscreenBoundary; - let offscreenChild = offscreenBoundary.child; - while (offscreenChild !== null) { - nextEffect = offscreenChild; - disappearLayoutEffects_begin(offscreenChild); - offscreenChild = offscreenChild.sibling; - } + // Disappear the layout effects of all the children + recursivelyTraverseDisappearLayoutEffects(offscreenBoundary); } } } else { @@ -2696,87 +2691,85 @@ function recursivelyTraverseLayoutEffects( setCurrentDebugFiberInDEV(prevDebugFiber); } -function disappearLayoutEffects_begin(subtreeRoot: Fiber) { - while (nextEffect !== null) { - const fiber = nextEffect; - const firstChild = fiber.child; - - // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic) - switch (fiber.tag) { - case FunctionComponent: - case ForwardRef: - case MemoComponent: - case SimpleMemoComponent: { - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - fiber.mode & ProfileMode - ) { - try { - startLayoutEffectTimer(); - commitHookEffectListUnmount(HookLayout, fiber, fiber.return); - } finally { - recordLayoutEffectDuration(fiber); - } - } else { - commitHookEffectListUnmount(HookLayout, fiber, fiber.return); - } - break; - } - case ClassComponent: { - // TODO (Offscreen) Check: flags & RefStatic - safelyDetachRef(fiber, fiber.return); - - const instance = fiber.stateNode; - if (typeof instance.componentWillUnmount === 'function') { - safelyCallComponentWillUnmount(fiber, fiber.return, instance); - } - break; - } - case HostComponent: { - safelyDetachRef(fiber, fiber.return); - break; - } - case OffscreenComponent: { - // Check if this is a - const isHidden = fiber.memoizedState !== null; - if (isHidden) { - // Nested Offscreen tree is already hidden. Don't disappear - // its effects. - disappearLayoutEffects_complete(subtreeRoot); - continue; +function disappearLayoutEffects(finishedWork: Fiber) { + switch (finishedWork.tag) { + case FunctionComponent: + case ForwardRef: + case MemoComponent: + case SimpleMemoComponent: { + // TODO (Offscreen) Check: flags & LayoutStatic + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + finishedWork.mode & ProfileMode + ) { + try { + startLayoutEffectTimer(); + commitHookEffectListUnmount( + HookLayout, + finishedWork, + finishedWork.return, + ); + } finally { + recordLayoutEffectDuration(finishedWork); } - break; + } else { + commitHookEffectListUnmount( + HookLayout, + finishedWork, + finishedWork.return, + ); } - } - // TODO (Offscreen) Check: subtreeFlags & LayoutStatic - if (firstChild !== null) { - firstChild.return = fiber; - nextEffect = firstChild; - } else { - disappearLayoutEffects_complete(subtreeRoot); + recursivelyTraverseDisappearLayoutEffects(finishedWork); + break; } - } -} + case ClassComponent: { + // TODO (Offscreen) Check: flags & RefStatic + safelyDetachRef(finishedWork, finishedWork.return); -function disappearLayoutEffects_complete(subtreeRoot: Fiber) { - while (nextEffect !== null) { - const fiber = nextEffect; + const instance = finishedWork.stateNode; + if (typeof instance.componentWillUnmount === 'function') { + safelyCallComponentWillUnmount( + finishedWork, + finishedWork.return, + instance, + ); + } - if (fiber === subtreeRoot) { - nextEffect = null; - return; + recursivelyTraverseDisappearLayoutEffects(finishedWork); + break; } + case HostComponent: { + // TODO (Offscreen) Check: flags & RefStatic + safelyDetachRef(finishedWork, finishedWork.return); - const sibling = fiber.sibling; - if (sibling !== null) { - sibling.return = fiber.return; - nextEffect = sibling; - return; + recursivelyTraverseDisappearLayoutEffects(finishedWork); + break; } + case OffscreenComponent: { + const isHidden = finishedWork.memoizedState !== null; + if (isHidden) { + // Nested Offscreen tree is already hidden. Don't disappear + // its effects. + } else { + recursivelyTraverseDisappearLayoutEffects(finishedWork); + } + break; + } + default: { + recursivelyTraverseDisappearLayoutEffects(finishedWork); + break; + } + } +} - nextEffect = fiber.return; +function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) { + // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic) + let child = parentFiber.child; + while (child !== null) { + disappearLayoutEffects(child); + child = child.sibling; } } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index a25a1ff3cd51d..d0efa065bdd90 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -2565,13 +2565,8 @@ function commitMutationEffectsOnFiber( if (isHidden) { if (!wasHidden) { if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) { - nextEffect = offscreenBoundary; - let offscreenChild = offscreenBoundary.child; - while (offscreenChild !== null) { - nextEffect = offscreenChild; - disappearLayoutEffects_begin(offscreenChild); - offscreenChild = offscreenChild.sibling; - } + // Disappear the layout effects of all the children + recursivelyTraverseDisappearLayoutEffects(offscreenBoundary); } } } else { @@ -2696,87 +2691,85 @@ function recursivelyTraverseLayoutEffects( setCurrentDebugFiberInDEV(prevDebugFiber); } -function disappearLayoutEffects_begin(subtreeRoot: Fiber) { - while (nextEffect !== null) { - const fiber = nextEffect; - const firstChild = fiber.child; - - // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic) - switch (fiber.tag) { - case FunctionComponent: - case ForwardRef: - case MemoComponent: - case SimpleMemoComponent: { - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - fiber.mode & ProfileMode - ) { - try { - startLayoutEffectTimer(); - commitHookEffectListUnmount(HookLayout, fiber, fiber.return); - } finally { - recordLayoutEffectDuration(fiber); - } - } else { - commitHookEffectListUnmount(HookLayout, fiber, fiber.return); - } - break; - } - case ClassComponent: { - // TODO (Offscreen) Check: flags & RefStatic - safelyDetachRef(fiber, fiber.return); - - const instance = fiber.stateNode; - if (typeof instance.componentWillUnmount === 'function') { - safelyCallComponentWillUnmount(fiber, fiber.return, instance); - } - break; - } - case HostComponent: { - safelyDetachRef(fiber, fiber.return); - break; - } - case OffscreenComponent: { - // Check if this is a - const isHidden = fiber.memoizedState !== null; - if (isHidden) { - // Nested Offscreen tree is already hidden. Don't disappear - // its effects. - disappearLayoutEffects_complete(subtreeRoot); - continue; +function disappearLayoutEffects(finishedWork: Fiber) { + switch (finishedWork.tag) { + case FunctionComponent: + case ForwardRef: + case MemoComponent: + case SimpleMemoComponent: { + // TODO (Offscreen) Check: flags & LayoutStatic + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + finishedWork.mode & ProfileMode + ) { + try { + startLayoutEffectTimer(); + commitHookEffectListUnmount( + HookLayout, + finishedWork, + finishedWork.return, + ); + } finally { + recordLayoutEffectDuration(finishedWork); } - break; + } else { + commitHookEffectListUnmount( + HookLayout, + finishedWork, + finishedWork.return, + ); } - } - // TODO (Offscreen) Check: subtreeFlags & LayoutStatic - if (firstChild !== null) { - firstChild.return = fiber; - nextEffect = firstChild; - } else { - disappearLayoutEffects_complete(subtreeRoot); + recursivelyTraverseDisappearLayoutEffects(finishedWork); + break; } - } -} + case ClassComponent: { + // TODO (Offscreen) Check: flags & RefStatic + safelyDetachRef(finishedWork, finishedWork.return); -function disappearLayoutEffects_complete(subtreeRoot: Fiber) { - while (nextEffect !== null) { - const fiber = nextEffect; + const instance = finishedWork.stateNode; + if (typeof instance.componentWillUnmount === 'function') { + safelyCallComponentWillUnmount( + finishedWork, + finishedWork.return, + instance, + ); + } - if (fiber === subtreeRoot) { - nextEffect = null; - return; + recursivelyTraverseDisappearLayoutEffects(finishedWork); + break; } + case HostComponent: { + // TODO (Offscreen) Check: flags & RefStatic + safelyDetachRef(finishedWork, finishedWork.return); - const sibling = fiber.sibling; - if (sibling !== null) { - sibling.return = fiber.return; - nextEffect = sibling; - return; + recursivelyTraverseDisappearLayoutEffects(finishedWork); + break; } + case OffscreenComponent: { + const isHidden = finishedWork.memoizedState !== null; + if (isHidden) { + // Nested Offscreen tree is already hidden. Don't disappear + // its effects. + } else { + recursivelyTraverseDisappearLayoutEffects(finishedWork); + } + break; + } + default: { + recursivelyTraverseDisappearLayoutEffects(finishedWork); + break; + } + } +} - nextEffect = fiber.return; +function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) { + // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic) + let child = parentFiber.child; + while (child !== null) { + disappearLayoutEffects(child); + child = child.sibling; } }