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.
Andrew Clark committed
Jul 13, 2022 at 11:45 UTC
697702bf341dcf63c47f6de9645970d45f6cf9ef
2 files changed
+142
-156
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+71
-78
@@ -2565,13 +2565,8 @@ function commitMutationEffectsOnFiber(
2565
if (isHidden) {
2566
if (!wasHidden) {
2567
if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) {
2568
- nextEffect = offscreenBoundary;
2569
- let offscreenChild = offscreenBoundary.child;
2570
- while (offscreenChild !== null) {
2571
- nextEffect = offscreenChild;
2572
- disappearLayoutEffects_begin(offscreenChild);
2573
- offscreenChild = offscreenChild.sibling;
2574
- }
2568
+ // Disappear the layout effects of all the children
2569
+ recursivelyTraverseDisappearLayoutEffects(offscreenBoundary);
2570
}
2571
}
2572
} else {
@@ -2696,87 +2691,85 @@ function recursivelyTraverseLayoutEffects(
2691
setCurrentDebugFiberInDEV(prevDebugFiber);
2692
}
2693
2699
-function disappearLayoutEffects_begin(subtreeRoot: Fiber) {
2700
- while (nextEffect !== null) {
2701
- const fiber = nextEffect;
2702
- const firstChild = fiber.child;
2703
-
2704
- // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
2705
- switch (fiber.tag) {
2706
- case FunctionComponent:
2707
- case ForwardRef:
2708
- case MemoComponent:
2709
- case SimpleMemoComponent: {
2710
- if (
2711
- enableProfilerTimer &&
2712
- enableProfilerCommitHooks &&
2713
- fiber.mode & ProfileMode
2714
- ) {
2715
- try {
2716
- startLayoutEffectTimer();
2717
- commitHookEffectListUnmount(HookLayout, fiber, fiber.return);
2718
- } finally {
2719
- recordLayoutEffectDuration(fiber);
2720
- }
2721
- } else {
2722
- commitHookEffectListUnmount(HookLayout, fiber, fiber.return);
2723
- }
2724
- break;
2725
- }
2726
- case ClassComponent: {
2727
- // TODO (Offscreen) Check: flags & RefStatic
2728
- safelyDetachRef(fiber, fiber.return);
2729
-
2730
- const instance = fiber.stateNode;
2731
- if (typeof instance.componentWillUnmount === 'function') {
2732
- safelyCallComponentWillUnmount(fiber, fiber.return, instance);
2733
- }
2734
- break;
2735
- }
2736
- case HostComponent: {
2737
- safelyDetachRef(fiber, fiber.return);
2738
- break;
2739
- }
2740
- case OffscreenComponent: {
2741
- // Check if this is a
2742
- const isHidden = fiber.memoizedState !== null;
2743
- if (isHidden) {
2744
- // Nested Offscreen tree is already hidden. Don't disappear
2745
- // its effects.
2746
- disappearLayoutEffects_complete(subtreeRoot);
2747
- continue;
2694
+function disappearLayoutEffects(finishedWork: Fiber) {
2695
+ switch (finishedWork.tag) {
2696
+ case FunctionComponent:
2697
+ case ForwardRef:
2698
+ case MemoComponent:
2699
+ case SimpleMemoComponent: {
2700
+ // TODO (Offscreen) Check: flags & LayoutStatic
2701
+ if (
2702
+ enableProfilerTimer &&
2703
+ enableProfilerCommitHooks &&
2704
+ finishedWork.mode & ProfileMode
2705
+ ) {
2706
+ try {
2707
+ startLayoutEffectTimer();
2708
+ commitHookEffectListUnmount(
2709
+ HookLayout,
2710
+ finishedWork,
2711
+ finishedWork.return,
2712
+ );
2713
+ } finally {
2714
+ recordLayoutEffectDuration(finishedWork);
2715
}
2749
- break;
2716
+ } else {
2717
+ commitHookEffectListUnmount(
2718
+ HookLayout,
2719
+ finishedWork,
2720
+ finishedWork.return,
2721
+ );
2722
}
2751
- }
2723
2753
- // TODO (Offscreen) Check: subtreeFlags & LayoutStatic
2754
- if (firstChild !== null) {
2755
- firstChild.return = fiber;
2756
- nextEffect = firstChild;
2757
- } else {
2758
- disappearLayoutEffects_complete(subtreeRoot);
2724
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
2725
+ break;
2726
}
2760
- }
2761
-}
2727
+ case ClassComponent: {
2728
+ // TODO (Offscreen) Check: flags & RefStatic
2729
+ safelyDetachRef(finishedWork, finishedWork.return);
2730
2763
-function disappearLayoutEffects_complete(subtreeRoot: Fiber) {
2764
- while (nextEffect !== null) {
2765
- const fiber = nextEffect;
2731
+ const instance = finishedWork.stateNode;
2732
+ if (typeof instance.componentWillUnmount === 'function') {
2733
+ safelyCallComponentWillUnmount(
2734
+ finishedWork,
2735
+ finishedWork.return,
2736
+ instance,
2737
+ );
2738
+ }
2739
2767
- if (fiber === subtreeRoot) {
2768
- nextEffect = null;
2769
- return;
2740
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
2741
+ break;
2742
}
2743
+ case HostComponent: {
2744
+ // TODO (Offscreen) Check: flags & RefStatic
2745
+ safelyDetachRef(finishedWork, finishedWork.return);
2746
2772
- const sibling = fiber.sibling;
2773
- if (sibling !== null) {
2774
- sibling.return = fiber.return;
2775
- nextEffect = sibling;
2776
- return;
2747
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
2748
+ break;
2749
}
2750
+ case OffscreenComponent: {
2751
+ const isHidden = finishedWork.memoizedState !== null;
2752
+ if (isHidden) {
2753
+ // Nested Offscreen tree is already hidden. Don't disappear
2754
+ // its effects.
2755
+ } else {
2756
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
2757
+ }
2758
+ break;
2759
+ }
2760
+ default: {
2761
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
2762
+ break;
2763
+ }
2764
+ }
2765
+}
2766
2779
- nextEffect = fiber.return;
2767
+function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
2768
+ // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
2769
+ let child = parentFiber.child;
2770
+ while (child !== null) {
2771
+ disappearLayoutEffects(child);
2772
+ child = child.sibling;
2773
}
2774
}
2775
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+71
-78
@@ -2565,13 +2565,8 @@ function commitMutationEffectsOnFiber(
2565
if (isHidden) {
2566
if (!wasHidden) {
2567
if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) {
2568
- nextEffect = offscreenBoundary;
2569
- let offscreenChild = offscreenBoundary.child;
2570
- while (offscreenChild !== null) {
2571
- nextEffect = offscreenChild;
2572
- disappearLayoutEffects_begin(offscreenChild);
2573
- offscreenChild = offscreenChild.sibling;
2574
- }
2568
+ // Disappear the layout effects of all the children
2569
+ recursivelyTraverseDisappearLayoutEffects(offscreenBoundary);
2570
}
2571
}
2572
} else {
@@ -2696,87 +2691,85 @@ function recursivelyTraverseLayoutEffects(
2691
setCurrentDebugFiberInDEV(prevDebugFiber);
2692
}
2693
2699
-function disappearLayoutEffects_begin(subtreeRoot: Fiber) {
2700
- while (nextEffect !== null) {
2701
- const fiber = nextEffect;
2702
- const firstChild = fiber.child;
2703
-
2704
- // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
2705
- switch (fiber.tag) {
2706
- case FunctionComponent:
2707
- case ForwardRef:
2708
- case MemoComponent:
2709
- case SimpleMemoComponent: {
2710
- if (
2711
- enableProfilerTimer &&
2712
- enableProfilerCommitHooks &&
2713
- fiber.mode & ProfileMode
2714
- ) {
2715
- try {
2716
- startLayoutEffectTimer();
2717
- commitHookEffectListUnmount(HookLayout, fiber, fiber.return);
2718
- } finally {
2719
- recordLayoutEffectDuration(fiber);
2720
- }
2721
- } else {
2722
- commitHookEffectListUnmount(HookLayout, fiber, fiber.return);
2723
- }
2724
- break;
2725
- }
2726
- case ClassComponent: {
2727
- // TODO (Offscreen) Check: flags & RefStatic
2728
- safelyDetachRef(fiber, fiber.return);
2729
-
2730
- const instance = fiber.stateNode;
2731
- if (typeof instance.componentWillUnmount === 'function') {
2732
- safelyCallComponentWillUnmount(fiber, fiber.return, instance);
2733
- }
2734
- break;
2735
- }
2736
- case HostComponent: {
2737
- safelyDetachRef(fiber, fiber.return);
2738
- break;
2739
- }
2740
- case OffscreenComponent: {
2741
- // Check if this is a
2742
- const isHidden = fiber.memoizedState !== null;
2743
- if (isHidden) {
2744
- // Nested Offscreen tree is already hidden. Don't disappear
2745
- // its effects.
2746
- disappearLayoutEffects_complete(subtreeRoot);
2747
- continue;
2694
+function disappearLayoutEffects(finishedWork: Fiber) {
2695
+ switch (finishedWork.tag) {
2696
+ case FunctionComponent:
2697
+ case ForwardRef:
2698
+ case MemoComponent:
2699
+ case SimpleMemoComponent: {
2700
+ // TODO (Offscreen) Check: flags & LayoutStatic
2701
+ if (
2702
+ enableProfilerTimer &&
2703
+ enableProfilerCommitHooks &&
2704
+ finishedWork.mode & ProfileMode
2705
+ ) {
2706
+ try {
2707
+ startLayoutEffectTimer();
2708
+ commitHookEffectListUnmount(
2709
+ HookLayout,
2710
+ finishedWork,
2711
+ finishedWork.return,
2712
+ );
2713
+ } finally {
2714
+ recordLayoutEffectDuration(finishedWork);
2715
}
2749
- break;
2716
+ } else {
2717
+ commitHookEffectListUnmount(
2718
+ HookLayout,
2719
+ finishedWork,
2720
+ finishedWork.return,
2721
+ );
2722
}
2751
- }
2723
2753
- // TODO (Offscreen) Check: subtreeFlags & LayoutStatic
2754
- if (firstChild !== null) {
2755
- firstChild.return = fiber;
2756
- nextEffect = firstChild;
2757
- } else {
2758
- disappearLayoutEffects_complete(subtreeRoot);
2724
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
2725
+ break;
2726
}
2760
- }
2761
-}
2727
+ case ClassComponent: {
2728
+ // TODO (Offscreen) Check: flags & RefStatic
2729
+ safelyDetachRef(finishedWork, finishedWork.return);
2730
2763
-function disappearLayoutEffects_complete(subtreeRoot: Fiber) {
2764
- while (nextEffect !== null) {
2765
- const fiber = nextEffect;
2731
+ const instance = finishedWork.stateNode;
2732
+ if (typeof instance.componentWillUnmount === 'function') {
2733
+ safelyCallComponentWillUnmount(
2734
+ finishedWork,
2735
+ finishedWork.return,
2736
+ instance,
2737
+ );
2738
+ }
2739
2767
- if (fiber === subtreeRoot) {
2768
- nextEffect = null;
2769
- return;
2740
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
2741
+ break;
2742
}
2743
+ case HostComponent: {
2744
+ // TODO (Offscreen) Check: flags & RefStatic
2745
+ safelyDetachRef(finishedWork, finishedWork.return);
2746
2772
- const sibling = fiber.sibling;
2773
- if (sibling !== null) {
2774
- sibling.return = fiber.return;
2775
- nextEffect = sibling;
2776
- return;
2747
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
2748
+ break;
2749
}
2750
+ case OffscreenComponent: {
2751
+ const isHidden = finishedWork.memoizedState !== null;
2752
+ if (isHidden) {
2753
+ // Nested Offscreen tree is already hidden. Don't disappear
2754
+ // its effects.
2755
+ } else {
2756
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
2757
+ }
2758
+ break;
2759
+ }
2760
+ default: {
2761
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
2762
+ break;
2763
+ }
2764
+ }
2765
+}
2766
2779
- nextEffect = fiber.return;
2767
+function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
2768
+ // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
2769
+ let child = parentFiber.child;
2770
+ while (child !== null) {
2771
+ disappearLayoutEffects(child);
2772
+ child = child.sibling;
2773
}
2774
}
2775