Skip to content

Commit

Permalink
Use recursion to traverse during "disappear layout" phase
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
acdlite committed Jul 19, 2022
1 parent 9929119 commit 697702b
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 156 deletions.
149 changes: 71 additions & 78 deletions packages/react-reconciler/src/ReactFiberCommitWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
}

Expand Down
149 changes: 71 additions & 78 deletions packages/react-reconciler/src/ReactFiberCommitWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
}

Expand Down

0 comments on commit 697702b

Please sign in to comment.