@samitouri / QOS-React-2 / commits / 697702bf34

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