Skip to content

Commit b641d02

Browse files
committed
Use recursion to traverse during layout phase
This converts the 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 the mutation phase. See 481dece for more context.
1 parent a1b1e39 commit b641d02

File tree

2 files changed

+186
-230
lines changed

2 files changed

+186
-230
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.new.js

+93-115
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,11 @@ function commitLayoutEffectOnFiber(
716716
case FunctionComponent:
717717
case ForwardRef:
718718
case SimpleMemoComponent: {
719+
recursivelyTraverseLayoutEffects(
720+
finishedRoot,
721+
finishedWork,
722+
committedLanes,
723+
);
719724
if (flags & Update) {
720725
if (!offscreenSubtreeWasHidden) {
721726
// At this point layout effects have already been destroyed (during mutation phase).
@@ -752,6 +757,11 @@ function commitLayoutEffectOnFiber(
752757
break;
753758
}
754759
case ClassComponent: {
760+
recursivelyTraverseLayoutEffects(
761+
finishedRoot,
762+
finishedWork,
763+
committedLanes,
764+
);
755765
if (flags & Update) {
756766
if (!offscreenSubtreeWasHidden) {
757767
const instance = finishedWork.stateNode;
@@ -946,6 +956,11 @@ function commitLayoutEffectOnFiber(
946956
break;
947957
}
948958
case HostRoot: {
959+
recursivelyTraverseLayoutEffects(
960+
finishedRoot,
961+
finishedWork,
962+
committedLanes,
963+
);
949964
if (flags & Callback) {
950965
// TODO: I think this is now always non-null by the time it reaches the
951966
// commit phase. Consider removing the type check.
@@ -974,6 +989,11 @@ function commitLayoutEffectOnFiber(
974989
break;
975990
}
976991
case HostComponent: {
992+
recursivelyTraverseLayoutEffects(
993+
finishedRoot,
994+
finishedWork,
995+
committedLanes,
996+
);
977997
if (flags & Update) {
978998
const instance: Instance = finishedWork.stateNode;
979999

@@ -1003,15 +1023,12 @@ function commitLayoutEffectOnFiber(
10031023
}
10041024
break;
10051025
}
1006-
case HostText: {
1007-
// We have no life-cycles associated with text.
1008-
break;
1009-
}
1010-
case HostPortal: {
1011-
// We have no life-cycles associated with portals.
1012-
break;
1013-
}
10141026
case Profiler: {
1027+
recursivelyTraverseLayoutEffects(
1028+
finishedRoot,
1029+
finishedWork,
1030+
committedLanes,
1031+
);
10151032
if (enableProfilerTimer) {
10161033
if (flags & Update) {
10171034
try {
@@ -1078,6 +1095,11 @@ function commitLayoutEffectOnFiber(
10781095
break;
10791096
}
10801097
case SuspenseComponent: {
1098+
recursivelyTraverseLayoutEffects(
1099+
finishedRoot,
1100+
finishedWork,
1101+
committedLanes,
1102+
);
10811103
if (flags & Update) {
10821104
try {
10831105
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
@@ -1087,20 +1109,58 @@ function commitLayoutEffectOnFiber(
10871109
}
10881110
break;
10891111
}
1090-
case SuspenseListComponent:
1091-
case IncompleteClassComponent:
1092-
case ScopeComponent:
1093-
case OffscreenComponent:
1094-
case LegacyHiddenComponent:
1095-
case TracingMarkerComponent: {
1112+
case OffscreenComponent: {
1113+
const isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode;
1114+
if (isModernRoot) {
1115+
const isHidden = finishedWork.memoizedState !== null;
1116+
const newOffscreenSubtreeIsHidden =
1117+
isHidden || offscreenSubtreeIsHidden;
1118+
if (newOffscreenSubtreeIsHidden) {
1119+
// The Offscreen tree is hidden. Skip over its layout effects.
1120+
} else {
1121+
// The Offscreen tree is visible.
1122+
1123+
const wasHidden = current !== null && current.memoizedState !== null;
1124+
const newOffscreenSubtreeWasHidden =
1125+
wasHidden || offscreenSubtreeWasHidden;
1126+
const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
1127+
const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
1128+
offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;
1129+
offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;
1130+
1131+
if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {
1132+
// This is the root of a reappearing boundary. Turn its layout
1133+
// effects back on.
1134+
// TODO: Convert this to use recursion
1135+
nextEffect = finishedWork;
1136+
reappearLayoutEffects_begin(finishedWork);
1137+
}
1138+
1139+
recursivelyTraverseLayoutEffects(
1140+
finishedRoot,
1141+
finishedWork,
1142+
committedLanes,
1143+
);
1144+
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
1145+
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
1146+
}
1147+
} else {
1148+
recursivelyTraverseLayoutEffects(
1149+
finishedRoot,
1150+
finishedWork,
1151+
committedLanes,
1152+
);
1153+
}
10961154
break;
10971155
}
1098-
1099-
default:
1100-
throw new Error(
1101-
'This unit of work tag should not have side-effects. This error is ' +
1102-
'likely caused by a bug in React. Please file an issue.',
1156+
default: {
1157+
recursivelyTraverseLayoutEffects(
1158+
finishedRoot,
1159+
finishedWork,
1160+
committedLanes,
11031161
);
1162+
break;
1163+
}
11041164
}
11051165
}
11061166

@@ -2591,112 +2651,30 @@ export function commitLayoutEffects(
25912651
): void {
25922652
inProgressLanes = committedLanes;
25932653
inProgressRoot = root;
2594-
nextEffect = finishedWork;
25952654

2596-
commitLayoutEffects_begin(finishedWork, root, committedLanes);
2655+
const current = finishedWork.alternate;
2656+
commitLayoutEffectOnFiber(root, current, finishedWork, committedLanes);
25972657

25982658
inProgressLanes = null;
25992659
inProgressRoot = null;
26002660
}
26012661

2602-
function commitLayoutEffects_begin(
2603-
subtreeRoot: Fiber,
2662+
function recursivelyTraverseLayoutEffects(
26042663
root: FiberRoot,
2605-
committedLanes: Lanes,
2606-
) {
2607-
// Suspense layout effects semantics don't change for legacy roots.
2608-
const isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode;
2609-
2610-
while (nextEffect !== null) {
2611-
const fiber = nextEffect;
2612-
const firstChild = fiber.child;
2613-
2614-
if (fiber.tag === OffscreenComponent && isModernRoot) {
2615-
// Keep track of the current Offscreen stack's state.
2616-
const isHidden = fiber.memoizedState !== null;
2617-
const newOffscreenSubtreeIsHidden = isHidden || offscreenSubtreeIsHidden;
2618-
if (newOffscreenSubtreeIsHidden) {
2619-
// The Offscreen tree is hidden. Skip over its layout effects.
2620-
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
2621-
continue;
2622-
} else {
2623-
// TODO (Offscreen) Also check: subtreeFlags & LayoutMask
2624-
const current = fiber.alternate;
2625-
const wasHidden = current !== null && current.memoizedState !== null;
2626-
const newOffscreenSubtreeWasHidden =
2627-
wasHidden || offscreenSubtreeWasHidden;
2628-
const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
2629-
const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
2630-
2631-
// Traverse the Offscreen subtree with the current Offscreen as the root.
2632-
offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;
2633-
offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;
2634-
2635-
if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {
2636-
// This is the root of a reappearing boundary. Turn its layout effects
2637-
// back on.
2638-
nextEffect = fiber;
2639-
reappearLayoutEffects_begin(fiber);
2640-
}
2641-
2642-
let child = firstChild;
2643-
while (child !== null) {
2644-
nextEffect = child;
2645-
commitLayoutEffects_begin(
2646-
child, // New root; bubble back up to here and stop.
2647-
root,
2648-
committedLanes,
2649-
);
2650-
child = child.sibling;
2651-
}
2652-
2653-
// Restore Offscreen state and resume in our-progress traversal.
2654-
nextEffect = fiber;
2655-
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
2656-
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
2657-
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
2658-
2659-
continue;
2660-
}
2661-
}
2662-
2663-
if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) {
2664-
firstChild.return = fiber;
2665-
nextEffect = firstChild;
2666-
} else {
2667-
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
2668-
}
2669-
}
2670-
}
2671-
2672-
function commitLayoutMountEffects_complete(
2673-
subtreeRoot: Fiber,
2674-
root: FiberRoot,
2675-
committedLanes: Lanes,
2664+
parentFiber: Fiber,
2665+
lanes: Lanes,
26762666
) {
2677-
while (nextEffect !== null) {
2678-
const fiber = nextEffect;
2679-
if ((fiber.flags & LayoutMask) !== NoFlags) {
2680-
const current = fiber.alternate;
2681-
setCurrentDebugFiberInDEV(fiber);
2682-
commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
2683-
resetCurrentDebugFiberInDEV();
2684-
}
2685-
2686-
if (fiber === subtreeRoot) {
2687-
nextEffect = null;
2688-
return;
2689-
}
2690-
2691-
const sibling = fiber.sibling;
2692-
if (sibling !== null) {
2693-
sibling.return = fiber.return;
2694-
nextEffect = sibling;
2695-
return;
2667+
const prevDebugFiber = getCurrentDebugFiberInDEV();
2668+
if (parentFiber.subtreeFlags & LayoutMask) {
2669+
let child = parentFiber.child;
2670+
while (child !== null) {
2671+
setCurrentDebugFiberInDEV(child);
2672+
const current = child.alternate;
2673+
commitLayoutEffectOnFiber(root, current, child, lanes);
2674+
child = child.sibling;
26962675
}
2697-
2698-
nextEffect = fiber.return;
26992676
}
2677+
setCurrentDebugFiberInDEV(prevDebugFiber);
27002678
}
27012679

27022680
function disappearLayoutEffects_begin(subtreeRoot: Fiber) {

0 commit comments

Comments
 (0)