Skip to content

Commit 41287d4

Browse files
committed
Use recursion to traverse during "reappear layout" phase
This converts the "reappear 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.
1 parent 697702b commit 41287d4

File tree

2 files changed

+154
-178
lines changed

2 files changed

+154
-178
lines changed

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

Lines changed: 77 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,11 +1132,12 @@ function commitLayoutEffectOnFiber(
11321132
if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {
11331133
// This is the root of a reappearing boundary. Turn its layout
11341134
// effects back on.
1135-
// TODO: Convert this to use recursion
1136-
nextEffect = finishedWork;
1137-
reappearLayoutEffects_begin(finishedWork);
1135+
recursivelyTraverseReappearLayoutEffects(finishedWork);
11381136
}
11391137

1138+
// TODO: We shouldn't traverse twice when reappearing layout effects.
1139+
// Move this into the else block of the above if statement, and modify
1140+
// reappearLayoutEffects to fire regular layout effects, too.
11401141
recursivelyTraverseLayoutEffects(
11411142
finishedRoot,
11421143
finishedWork,
@@ -1165,48 +1166,6 @@ function commitLayoutEffectOnFiber(
11651166
}
11661167
}
11671168

1168-
function reappearLayoutEffectsOnFiber(node: Fiber) {
1169-
// Turn on layout effects in a tree that previously disappeared.
1170-
// TODO (Offscreen) Check: flags & LayoutStatic
1171-
switch (node.tag) {
1172-
case FunctionComponent:
1173-
case ForwardRef:
1174-
case SimpleMemoComponent: {
1175-
if (
1176-
enableProfilerTimer &&
1177-
enableProfilerCommitHooks &&
1178-
node.mode & ProfileMode
1179-
) {
1180-
try {
1181-
startLayoutEffectTimer();
1182-
safelyCallCommitHookLayoutEffectListMount(node, node.return);
1183-
} finally {
1184-
recordLayoutEffectDuration(node);
1185-
}
1186-
} else {
1187-
safelyCallCommitHookLayoutEffectListMount(node, node.return);
1188-
}
1189-
break;
1190-
}
1191-
case ClassComponent: {
1192-
const instance = node.stateNode;
1193-
if (typeof instance.componentDidMount === 'function') {
1194-
safelyCallComponentDidMount(node, node.return, instance);
1195-
}
1196-
safelyAttachRef(node, node.return);
1197-
const updateQueue: UpdateQueue<*> | null = (node.updateQueue: any);
1198-
if (updateQueue !== null) {
1199-
commitHiddenCallbacks(updateQueue, instance);
1200-
}
1201-
break;
1202-
}
1203-
case HostComponent: {
1204-
safelyAttachRef(node, node.return);
1205-
break;
1206-
}
1207-
}
1208-
}
1209-
12101169
function commitTransitionProgress(offscreenFiber: Fiber) {
12111170
if (enableTransitionTracing) {
12121171
// This function adds suspense boundaries to the root
@@ -2773,60 +2732,89 @@ function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
27732732
}
27742733
}
27752734

2776-
function reappearLayoutEffects_begin(subtreeRoot: Fiber) {
2777-
while (nextEffect !== null) {
2778-
const fiber = nextEffect;
2779-
const firstChild = fiber.child;
2735+
function reappearLayoutEffects(finishedWork: Fiber) {
2736+
// Turn on layout effects in a tree that previously disappeared.
2737+
// TODO (Offscreen) Check: flags & LayoutStatic
2738+
switch (finishedWork.tag) {
2739+
case FunctionComponent:
2740+
case ForwardRef:
2741+
case SimpleMemoComponent: {
2742+
recursivelyTraverseReappearLayoutEffects(finishedWork);
27802743

2781-
if (fiber.tag === OffscreenComponent) {
2782-
const isHidden = fiber.memoizedState !== null;
2783-
if (isHidden) {
2784-
// Nested Offscreen tree is still hidden. Don't re-appear its effects.
2785-
reappearLayoutEffects_complete(subtreeRoot);
2786-
continue;
2744+
// TODO: Check for LayoutStatic flag
2745+
if (
2746+
enableProfilerTimer &&
2747+
enableProfilerCommitHooks &&
2748+
finishedWork.mode & ProfileMode
2749+
) {
2750+
try {
2751+
startLayoutEffectTimer();
2752+
safelyCallCommitHookLayoutEffectListMount(
2753+
finishedWork,
2754+
finishedWork.return,
2755+
);
2756+
} finally {
2757+
recordLayoutEffectDuration(finishedWork);
2758+
}
2759+
} else {
2760+
safelyCallCommitHookLayoutEffectListMount(
2761+
finishedWork,
2762+
finishedWork.return,
2763+
);
27872764
}
2765+
break;
27882766
}
2767+
case ClassComponent: {
2768+
recursivelyTraverseReappearLayoutEffects(finishedWork);
27892769

2790-
// TODO (Offscreen) Check: subtreeFlags & LayoutStatic
2791-
if (firstChild !== null) {
2792-
// This node may have been reused from a previous render, so we can't
2793-
// assume its return pointer is correct.
2794-
firstChild.return = fiber;
2795-
nextEffect = firstChild;
2796-
} else {
2797-
reappearLayoutEffects_complete(subtreeRoot);
2770+
const instance = finishedWork.stateNode;
2771+
// TODO: Check for LayoutStatic flag
2772+
if (typeof instance.componentDidMount === 'function') {
2773+
safelyCallComponentDidMount(
2774+
finishedWork,
2775+
finishedWork.return,
2776+
instance,
2777+
);
2778+
}
2779+
// TODO: Check for RefStatic flag
2780+
safelyAttachRef(finishedWork, finishedWork.return);
2781+
const updateQueue: UpdateQueue<
2782+
*,
2783+
> | null = (finishedWork.updateQueue: any);
2784+
if (updateQueue !== null) {
2785+
commitHiddenCallbacks(updateQueue, instance);
2786+
}
2787+
break;
27982788
}
2799-
}
2800-
}
2801-
2802-
function reappearLayoutEffects_complete(subtreeRoot: Fiber) {
2803-
while (nextEffect !== null) {
2804-
const fiber = nextEffect;
2789+
case HostComponent: {
2790+
recursivelyTraverseReappearLayoutEffects(finishedWork);
28052791

2806-
// TODO (Offscreen) Check: flags & LayoutStatic
2807-
setCurrentDebugFiberInDEV(fiber);
2808-
try {
2809-
reappearLayoutEffectsOnFiber(fiber);
2810-
} catch (error) {
2811-
captureCommitPhaseError(fiber, fiber.return, error);
2792+
// TODO: Check for RefStatic flag
2793+
safelyAttachRef(finishedWork, finishedWork.return);
2794+
break;
28122795
}
2813-
resetCurrentDebugFiberInDEV();
2814-
2815-
if (fiber === subtreeRoot) {
2816-
nextEffect = null;
2817-
return;
2796+
case OffscreenComponent: {
2797+
const isHidden = finishedWork.memoizedState !== null;
2798+
if (isHidden) {
2799+
// Nested Offscreen tree is still hidden. Don't re-appear its effects.
2800+
} else {
2801+
recursivelyTraverseReappearLayoutEffects(finishedWork);
2802+
}
2803+
break;
28182804
}
2819-
2820-
const sibling = fiber.sibling;
2821-
if (sibling !== null) {
2822-
// This node may have been reused from a previous render, so we can't
2823-
// assume its return pointer is correct.
2824-
sibling.return = fiber.return;
2825-
nextEffect = sibling;
2826-
return;
2805+
default: {
2806+
recursivelyTraverseReappearLayoutEffects(finishedWork);
2807+
break;
28272808
}
2809+
}
2810+
}
28282811

2829-
nextEffect = fiber.return;
2812+
function recursivelyTraverseReappearLayoutEffects(parentFiber: Fiber) {
2813+
// TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
2814+
let child = parentFiber.child;
2815+
while (child !== null) {
2816+
reappearLayoutEffects(child);
2817+
child = child.sibling;
28302818
}
28312819
}
28322820

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

Lines changed: 77 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,11 +1132,12 @@ function commitLayoutEffectOnFiber(
11321132
if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {
11331133
// This is the root of a reappearing boundary. Turn its layout
11341134
// effects back on.
1135-
// TODO: Convert this to use recursion
1136-
nextEffect = finishedWork;
1137-
reappearLayoutEffects_begin(finishedWork);
1135+
recursivelyTraverseReappearLayoutEffects(finishedWork);
11381136
}
11391137

1138+
// TODO: We shouldn't traverse twice when reappearing layout effects.
1139+
// Move this into the else block of the above if statement, and modify
1140+
// reappearLayoutEffects to fire regular layout effects, too.
11401141
recursivelyTraverseLayoutEffects(
11411142
finishedRoot,
11421143
finishedWork,
@@ -1165,48 +1166,6 @@ function commitLayoutEffectOnFiber(
11651166
}
11661167
}
11671168

1168-
function reappearLayoutEffectsOnFiber(node: Fiber) {
1169-
// Turn on layout effects in a tree that previously disappeared.
1170-
// TODO (Offscreen) Check: flags & LayoutStatic
1171-
switch (node.tag) {
1172-
case FunctionComponent:
1173-
case ForwardRef:
1174-
case SimpleMemoComponent: {
1175-
if (
1176-
enableProfilerTimer &&
1177-
enableProfilerCommitHooks &&
1178-
node.mode & ProfileMode
1179-
) {
1180-
try {
1181-
startLayoutEffectTimer();
1182-
safelyCallCommitHookLayoutEffectListMount(node, node.return);
1183-
} finally {
1184-
recordLayoutEffectDuration(node);
1185-
}
1186-
} else {
1187-
safelyCallCommitHookLayoutEffectListMount(node, node.return);
1188-
}
1189-
break;
1190-
}
1191-
case ClassComponent: {
1192-
const instance = node.stateNode;
1193-
if (typeof instance.componentDidMount === 'function') {
1194-
safelyCallComponentDidMount(node, node.return, instance);
1195-
}
1196-
safelyAttachRef(node, node.return);
1197-
const updateQueue: UpdateQueue<*> | null = (node.updateQueue: any);
1198-
if (updateQueue !== null) {
1199-
commitHiddenCallbacks(updateQueue, instance);
1200-
}
1201-
break;
1202-
}
1203-
case HostComponent: {
1204-
safelyAttachRef(node, node.return);
1205-
break;
1206-
}
1207-
}
1208-
}
1209-
12101169
function commitTransitionProgress(offscreenFiber: Fiber) {
12111170
if (enableTransitionTracing) {
12121171
// This function adds suspense boundaries to the root
@@ -2773,60 +2732,89 @@ function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
27732732
}
27742733
}
27752734

2776-
function reappearLayoutEffects_begin(subtreeRoot: Fiber) {
2777-
while (nextEffect !== null) {
2778-
const fiber = nextEffect;
2779-
const firstChild = fiber.child;
2735+
function reappearLayoutEffects(finishedWork: Fiber) {
2736+
// Turn on layout effects in a tree that previously disappeared.
2737+
// TODO (Offscreen) Check: flags & LayoutStatic
2738+
switch (finishedWork.tag) {
2739+
case FunctionComponent:
2740+
case ForwardRef:
2741+
case SimpleMemoComponent: {
2742+
recursivelyTraverseReappearLayoutEffects(finishedWork);
27802743

2781-
if (fiber.tag === OffscreenComponent) {
2782-
const isHidden = fiber.memoizedState !== null;
2783-
if (isHidden) {
2784-
// Nested Offscreen tree is still hidden. Don't re-appear its effects.
2785-
reappearLayoutEffects_complete(subtreeRoot);
2786-
continue;
2744+
// TODO: Check for LayoutStatic flag
2745+
if (
2746+
enableProfilerTimer &&
2747+
enableProfilerCommitHooks &&
2748+
finishedWork.mode & ProfileMode
2749+
) {
2750+
try {
2751+
startLayoutEffectTimer();
2752+
safelyCallCommitHookLayoutEffectListMount(
2753+
finishedWork,
2754+
finishedWork.return,
2755+
);
2756+
} finally {
2757+
recordLayoutEffectDuration(finishedWork);
2758+
}
2759+
} else {
2760+
safelyCallCommitHookLayoutEffectListMount(
2761+
finishedWork,
2762+
finishedWork.return,
2763+
);
27872764
}
2765+
break;
27882766
}
2767+
case ClassComponent: {
2768+
recursivelyTraverseReappearLayoutEffects(finishedWork);
27892769

2790-
// TODO (Offscreen) Check: subtreeFlags & LayoutStatic
2791-
if (firstChild !== null) {
2792-
// This node may have been reused from a previous render, so we can't
2793-
// assume its return pointer is correct.
2794-
firstChild.return = fiber;
2795-
nextEffect = firstChild;
2796-
} else {
2797-
reappearLayoutEffects_complete(subtreeRoot);
2770+
const instance = finishedWork.stateNode;
2771+
// TODO: Check for LayoutStatic flag
2772+
if (typeof instance.componentDidMount === 'function') {
2773+
safelyCallComponentDidMount(
2774+
finishedWork,
2775+
finishedWork.return,
2776+
instance,
2777+
);
2778+
}
2779+
// TODO: Check for RefStatic flag
2780+
safelyAttachRef(finishedWork, finishedWork.return);
2781+
const updateQueue: UpdateQueue<
2782+
*,
2783+
> | null = (finishedWork.updateQueue: any);
2784+
if (updateQueue !== null) {
2785+
commitHiddenCallbacks(updateQueue, instance);
2786+
}
2787+
break;
27982788
}
2799-
}
2800-
}
2801-
2802-
function reappearLayoutEffects_complete(subtreeRoot: Fiber) {
2803-
while (nextEffect !== null) {
2804-
const fiber = nextEffect;
2789+
case HostComponent: {
2790+
recursivelyTraverseReappearLayoutEffects(finishedWork);
28052791

2806-
// TODO (Offscreen) Check: flags & LayoutStatic
2807-
setCurrentDebugFiberInDEV(fiber);
2808-
try {
2809-
reappearLayoutEffectsOnFiber(fiber);
2810-
} catch (error) {
2811-
captureCommitPhaseError(fiber, fiber.return, error);
2792+
// TODO: Check for RefStatic flag
2793+
safelyAttachRef(finishedWork, finishedWork.return);
2794+
break;
28122795
}
2813-
resetCurrentDebugFiberInDEV();
2814-
2815-
if (fiber === subtreeRoot) {
2816-
nextEffect = null;
2817-
return;
2796+
case OffscreenComponent: {
2797+
const isHidden = finishedWork.memoizedState !== null;
2798+
if (isHidden) {
2799+
// Nested Offscreen tree is still hidden. Don't re-appear its effects.
2800+
} else {
2801+
recursivelyTraverseReappearLayoutEffects(finishedWork);
2802+
}
2803+
break;
28182804
}
2819-
2820-
const sibling = fiber.sibling;
2821-
if (sibling !== null) {
2822-
// This node may have been reused from a previous render, so we can't
2823-
// assume its return pointer is correct.
2824-
sibling.return = fiber.return;
2825-
nextEffect = sibling;
2826-
return;
2805+
default: {
2806+
recursivelyTraverseReappearLayoutEffects(finishedWork);
2807+
break;
28272808
}
2809+
}
2810+
}
28282811

2829-
nextEffect = fiber.return;
2812+
function recursivelyTraverseReappearLayoutEffects(parentFiber: Fiber) {
2813+
// TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
2814+
let child = parentFiber.child;
2815+
while (child !== null) {
2816+
reappearLayoutEffects(child);
2817+
child = child.sibling;
28302818
}
28312819
}
28322820

0 commit comments

Comments
 (0)