Skip to content

Commit 9e71962

Browse files
committed
Convert passive unmount phase to tree traversal
1 parent 1b09080 commit 9e71962

File tree

3 files changed

+182
-89
lines changed

3 files changed

+182
-89
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
363363
// We assume pendingProps, index, key, ref, return are still untouched to
364364
// avoid doing another reconciliation.
365365

366-
// Reset the effect tag but keep any Placement tags, since that's something
366+
// Reset the effect flags but keep any Placement tags, since that's something
367367
// that child fiber is setting, not the reconciliation.
368368
workInProgress.flags &= Placement;
369369

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

Lines changed: 155 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
2424
import type {Wakeable} from 'shared/ReactTypes';
2525
import type {ReactPriorityLevel} from './ReactInternalTypes';
2626
import type {OffscreenState} from './ReactFiberOffscreenComponent';
27+
import type {HookFlags} from './ReactHookEffectTags';
2728

2829
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
2930
import {
@@ -65,10 +66,13 @@ import {
6566
NoFlags,
6667
ContentReset,
6768
Placement,
69+
ChildDeletion,
6870
Snapshot,
6971
Update,
7072
Passive,
73+
PassiveStatic,
7174
PassiveMask,
75+
PassiveUnmountPendingDev,
7276
} from './ReactFiberFlags';
7377
import getComponentName from 'shared/getComponentName';
7478
import invariant from 'shared/invariant';
@@ -340,19 +344,19 @@ function commitBeforeMutationLifeCycles(
340344
);
341345
}
342346

343-
function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
347+
function commitHookEffectListUnmount(flags: HookFlags, finishedWork: Fiber) {
344348
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
345349
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
346350
if (lastEffect !== null) {
347351
const firstEffect = lastEffect.next;
348352
let effect = firstEffect;
349353
do {
350-
if ((effect.tag & tag) === tag) {
354+
if ((effect.tag & flags) === flags) {
351355
// Unmount
352356
const destroy = effect.destroy;
353357
effect.destroy = undefined;
354358
if (destroy !== undefined) {
355-
destroy();
359+
safelyCallDestroy(finishedWork, destroy);
356360
}
357361
}
358362
effect = effect.next;
@@ -1914,6 +1918,154 @@ function commitPassiveMountOnFiber(
19141918
}
19151919
}
19161920

1921+
export function commitPassiveUnmountEffects(firstChild: Fiber): void {
1922+
nextEffect = firstChild;
1923+
commitPassiveUnmountEffects_begin();
1924+
}
1925+
1926+
function commitPassiveUnmountEffects_begin() {
1927+
while (nextEffect !== null) {
1928+
const fiber = nextEffect;
1929+
const child = fiber.child;
1930+
1931+
if ((nextEffect.flags & ChildDeletion) !== NoFlags) {
1932+
const deletions = fiber.deletions;
1933+
if (deletions !== null) {
1934+
for (let i = 0; i < deletions.length; i++) {
1935+
const fiberToDelete = deletions[i];
1936+
nextEffect = fiberToDelete;
1937+
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(fiberToDelete);
1938+
1939+
// Now that passive effects have been processed, it's safe to detach lingering pointers.
1940+
detachFiberAfterEffects(fiberToDelete);
1941+
}
1942+
nextEffect = fiber;
1943+
}
1944+
}
1945+
1946+
if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && child !== null) {
1947+
ensureCorrectReturnPointer(child, fiber);
1948+
nextEffect = child;
1949+
} else {
1950+
commitPassiveUnmountEffects_complete();
1951+
}
1952+
}
1953+
}
1954+
1955+
function commitPassiveUnmountEffects_complete() {
1956+
while (nextEffect !== null) {
1957+
const fiber = nextEffect;
1958+
if ((fiber.flags & Passive) !== NoFlags) {
1959+
setCurrentDebugFiberInDEV(fiber);
1960+
commitPassiveUnmountOnFiber(fiber);
1961+
resetCurrentDebugFiberInDEV();
1962+
}
1963+
1964+
const sibling = fiber.sibling;
1965+
if (sibling !== null) {
1966+
ensureCorrectReturnPointer(sibling, fiber.return);
1967+
nextEffect = sibling;
1968+
return;
1969+
}
1970+
1971+
nextEffect = fiber.return;
1972+
}
1973+
}
1974+
1975+
function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
1976+
if (__DEV__) {
1977+
finishedWork.flags &= ~PassiveUnmountPendingDev;
1978+
const alternate = finishedWork.alternate;
1979+
if (alternate !== null) {
1980+
alternate.flags &= ~PassiveUnmountPendingDev;
1981+
}
1982+
}
1983+
1984+
switch (finishedWork.tag) {
1985+
case FunctionComponent:
1986+
case ForwardRef:
1987+
case SimpleMemoComponent: {
1988+
if (
1989+
enableProfilerTimer &&
1990+
enableProfilerCommitHooks &&
1991+
finishedWork.mode & ProfileMode
1992+
) {
1993+
startPassiveEffectTimer();
1994+
commitHookEffectListUnmount(HookPassive | HookHasEffect, finishedWork);
1995+
recordPassiveEffectDuration(finishedWork);
1996+
} else {
1997+
commitHookEffectListUnmount(HookPassive | HookHasEffect, finishedWork);
1998+
}
1999+
break;
2000+
}
2001+
}
2002+
}
2003+
2004+
function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
2005+
deletedSubtreeRoot: Fiber,
2006+
) {
2007+
while (nextEffect !== null) {
2008+
const fiber = nextEffect;
2009+
const child = fiber.child;
2010+
if ((fiber.subtreeFlags & PassiveStatic) !== NoFlags && child !== null) {
2011+
ensureCorrectReturnPointer(child, fiber);
2012+
nextEffect = child;
2013+
} else {
2014+
commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
2015+
deletedSubtreeRoot,
2016+
);
2017+
}
2018+
}
2019+
}
2020+
2021+
function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
2022+
deletedSubtreeRoot: Fiber,
2023+
) {
2024+
while (nextEffect !== null) {
2025+
const fiber = nextEffect;
2026+
if ((fiber.flags & PassiveStatic) !== NoFlags) {
2027+
setCurrentDebugFiberInDEV(fiber);
2028+
commitPassiveUnmountInsideDeletedTreeOnFiber(fiber);
2029+
resetCurrentDebugFiberInDEV();
2030+
}
2031+
2032+
if (fiber === deletedSubtreeRoot) {
2033+
nextEffect = null;
2034+
return;
2035+
}
2036+
2037+
const sibling = fiber.sibling;
2038+
if (sibling !== null) {
2039+
ensureCorrectReturnPointer(sibling, fiber.return);
2040+
nextEffect = sibling;
2041+
return;
2042+
}
2043+
2044+
nextEffect = fiber.return;
2045+
}
2046+
}
2047+
2048+
function commitPassiveUnmountInsideDeletedTreeOnFiber(current: Fiber): void {
2049+
switch (current.tag) {
2050+
case FunctionComponent:
2051+
case ForwardRef:
2052+
case SimpleMemoComponent: {
2053+
if (
2054+
enableProfilerTimer &&
2055+
enableProfilerCommitHooks &&
2056+
current.mode & ProfileMode
2057+
) {
2058+
startPassiveEffectTimer();
2059+
commitHookEffectListUnmount(HookPassive, current);
2060+
recordPassiveEffectDuration(current);
2061+
} else {
2062+
commitHookEffectListUnmount(HookPassive, current);
2063+
}
2064+
break;
2065+
}
2066+
}
2067+
}
2068+
19172069
let didWarnWrongReturnPointer = false;
19182070
function ensureCorrectReturnPointer(fiber, expectedReturnFiber) {
19192071
if (__DEV__) {

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

Lines changed: 26 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {Interaction} from 'scheduler/src/Tracing';
1515
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
1616
import type {Effect as HookEffect} from './ReactFiberHooks.new';
1717
import type {StackCursor} from './ReactFiberStack.new';
18+
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
1819

1920
import {
2021
warnAboutDeprecatedLifecycles,
@@ -51,6 +52,10 @@ import {
5152
flushSyncCallbackQueue,
5253
scheduleSyncCallback,
5354
} from './SchedulerWithReactIntegration.new';
55+
import {
56+
NoFlags as NoHookEffect,
57+
Passive as HookPassive,
58+
} from './ReactHookEffectTags';
5459
import {
5560
logCommitStarted,
5661
logCommitStopped,
@@ -128,7 +133,7 @@ import {
128133
Snapshot,
129134
Callback,
130135
Passive,
131-
PassiveUnmountPendingDev,
136+
PassiveStatic,
132137
Incomplete,
133138
HostEffectMask,
134139
Hydrating,
@@ -195,6 +200,7 @@ import {
195200
commitResetTextContent,
196201
isSuspenseBoundaryBeingHidden,
197202
commitPassiveMountEffects,
203+
commitPassiveUnmountEffects,
198204
detachFiberAfterEffects,
199205
} from './ReactFiberCommitWork.new';
200206
import {enqueueUpdate} from './ReactUpdateQueue.new';
@@ -214,9 +220,7 @@ import {
214220
import {
215221
markNestedUpdateScheduled,
216222
recordCommitTime,
217-
recordPassiveEffectDuration,
218223
resetNestedUpdateFlag,
219-
startPassiveEffectTimer,
220224
startProfilerTimer,
221225
stopProfilerTimerIfRunningAndRecordDelta,
222226
syncNestedUpdateFlag,
@@ -342,7 +346,6 @@ let rootDoesHavePassiveEffects: boolean = false;
342346
let rootWithPendingPassiveEffects: FiberRoot | null = null;
343347
let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority;
344348
let pendingPassiveEffectsLanes: Lanes = NoLanes;
345-
let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = [];
346349
let pendingPassiveProfilerEffects: Array<Fiber> = [];
347350

348351
let rootsWithPendingDiscreteUpdates: Set<FiberRoot> | null = null;
@@ -2512,14 +2515,6 @@ export function enqueuePendingPassiveHookEffectUnmount(
25122515
fiber: Fiber,
25132516
effect: HookEffect,
25142517
): void {
2515-
pendingPassiveHookEffectsUnmount.push(effect, fiber);
2516-
if (__DEV__) {
2517-
fiber.flags |= PassiveUnmountPendingDev;
2518-
const alternate = fiber.alternate;
2519-
if (alternate !== null) {
2520-
alternate.flags |= PassiveUnmountPendingDev;
2521-
}
2522-
}
25232518
if (!rootDoesHavePassiveEffects) {
25242519
rootDoesHavePassiveEffects = true;
25252520
scheduleCallback(NormalSchedulerPriority, () => {
@@ -2562,74 +2557,7 @@ function flushPassiveEffectsImpl() {
25622557
executionContext |= CommitContext;
25632558
const prevInteractions = pushInteractions(root);
25642559

2565-
// It's important that ALL pending passive effect destroy functions are called
2566-
// before ANY passive effect create functions are called.
2567-
// Otherwise effects in sibling components might interfere with each other.
2568-
// e.g. a destroy function in one component may unintentionally override a ref
2569-
// value set by a create function in another component.
2570-
// Layout effects have the same constraint.
2571-
2572-
// First pass: Destroy stale passive effects.
2573-
const unmountEffects = pendingPassiveHookEffectsUnmount;
2574-
pendingPassiveHookEffectsUnmount = [];
2575-
for (let i = 0; i < unmountEffects.length; i += 2) {
2576-
const effect = ((unmountEffects[i]: any): HookEffect);
2577-
const fiber = ((unmountEffects[i + 1]: any): Fiber);
2578-
const destroy = effect.destroy;
2579-
effect.destroy = undefined;
2580-
2581-
if (__DEV__) {
2582-
fiber.flags &= ~PassiveUnmountPendingDev;
2583-
const alternate = fiber.alternate;
2584-
if (alternate !== null) {
2585-
alternate.flags &= ~PassiveUnmountPendingDev;
2586-
}
2587-
}
2588-
2589-
if (typeof destroy === 'function') {
2590-
if (__DEV__) {
2591-
setCurrentDebugFiberInDEV(fiber);
2592-
if (
2593-
enableProfilerTimer &&
2594-
enableProfilerCommitHooks &&
2595-
fiber.mode & ProfileMode
2596-
) {
2597-
startPassiveEffectTimer();
2598-
invokeGuardedCallback(null, destroy, null);
2599-
recordPassiveEffectDuration(fiber);
2600-
} else {
2601-
invokeGuardedCallback(null, destroy, null);
2602-
}
2603-
if (hasCaughtError()) {
2604-
invariant(fiber !== null, 'Should be working on an effect.');
2605-
const error = clearCaughtError();
2606-
captureCommitPhaseError(fiber, error);
2607-
}
2608-
resetCurrentDebugFiberInDEV();
2609-
} else {
2610-
try {
2611-
if (
2612-
enableProfilerTimer &&
2613-
enableProfilerCommitHooks &&
2614-
fiber.mode & ProfileMode
2615-
) {
2616-
try {
2617-
startPassiveEffectTimer();
2618-
destroy();
2619-
} finally {
2620-
recordPassiveEffectDuration(fiber);
2621-
}
2622-
} else {
2623-
destroy();
2624-
}
2625-
} catch (error) {
2626-
invariant(fiber !== null, 'Should be working on an effect.');
2627-
captureCommitPhaseError(fiber, error);
2628-
}
2629-
}
2630-
}
2631-
}
2632-
// Second pass: Create new passive effects.
2560+
commitPassiveUnmountEffects(root.current);
26332561
commitPassiveMountEffects(root, root.current);
26342562

26352563
// TODO: Move to commitPassiveMountEffects
@@ -3017,12 +2945,25 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
30172945
return;
30182946
}
30192947

3020-
// If there are pending passive effects unmounts for this Fiber,
3021-
// we can assume that they would have prevented this update.
3022-
if ((fiber.flags & PassiveUnmountPendingDev) !== NoFlags) {
3023-
return;
3024-
}
2948+
if ((fiber.flags & PassiveStatic) !== NoFlags) {
2949+
const updateQueue: FunctionComponentUpdateQueue | null = (fiber.updateQueue: any);
2950+
if (updateQueue !== null) {
2951+
const lastEffect = updateQueue.lastEffect;
2952+
if (lastEffect !== null) {
2953+
const firstEffect = lastEffect.next;
30252954

2955+
let effect = firstEffect;
2956+
do {
2957+
if (effect.destroy !== undefined) {
2958+
if ((effect.tag & HookPassive) !== NoHookEffect) {
2959+
return;
2960+
}
2961+
}
2962+
effect = effect.next;
2963+
} while (effect !== firstEffect);
2964+
}
2965+
}
2966+
}
30262967
// We show the whole stack but dedupe on the top component's name because
30272968
// the problematic code almost always lies inside that component.
30282969
const componentName = getComponentName(fiber.type) || 'ReactComponent';

0 commit comments

Comments
 (0)