Convert passive unmount phase to tree traversal
Andrew Clark committed
Dec 7, 2020 at 18:01 UTC
1cf59f34b89728f8ef2bc17158c438ab17f063bd
6 files changed
+364
-178
packages/react-reconciler/src/ReactFiber.new.js
+1
-1
@@ -363,7 +363,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
363
// We assume pendingProps, index, key, ref, return are still untouched to
364
// avoid doing another reconciliation.
365
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
367
// that child fiber is setting, not the reconciliation.
368
workInProgress.flags &= Placement;
369
packages/react-reconciler/src/ReactFiber.old.js
+1
-1
@@ -363,7 +363,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
363
// We assume pendingProps, index, key, ref, return are still untouched to
364
// avoid doing another reconciliation.
365
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
367
// that child fiber is setting, not the reconciliation.
368
workInProgress.flags &= Placement;
369
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+155
-3
@@ -24,6 +24,7 @@ import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
24
import type {Wakeable} from 'shared/ReactTypes';
25
import type {ReactPriorityLevel} from './ReactInternalTypes';
26
import type {OffscreenState} from './ReactFiberOffscreenComponent';
27
+import type {HookFlags} from './ReactHookEffectTags';
28
29
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
30
import {
@@ -65,10 +66,13 @@ import {
66
NoFlags,
67
ContentReset,
68
Placement,
69
+ ChildDeletion,
70
Snapshot,
71
Update,
72
Passive,
73
+ PassiveStatic,
74
PassiveMask,
75
+ PassiveUnmountPendingDev,
76
} from './ReactFiberFlags';
77
import getComponentName from 'shared/getComponentName';
78
import invariant from 'shared/invariant';
@@ -340,19 +344,19 @@ function commitBeforeMutationLifeCycles(
344
);
345
}
346
343
-function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
347
+function commitHookEffectListUnmount(flags: HookFlags, finishedWork: Fiber) {
348
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
349
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
350
if (lastEffect !== null) {
351
const firstEffect = lastEffect.next;
352
let effect = firstEffect;
353
do {
350
- if ((effect.tag & tag) === tag) {
354
+ if ((effect.tag & flags) === flags) {
355
// Unmount
356
const destroy = effect.destroy;
357
effect.destroy = undefined;
358
if (destroy !== undefined) {
355
- destroy();
359
+ safelyCallDestroy(finishedWork, destroy);
360
}
361
}
362
effect = effect.next;
@@ -1914,6 +1918,154 @@ function commitPassiveMountOnFiber(
1918
}
1919
}
1920
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
+
2069
let didWarnWrongReturnPointer = false;
2070
function ensureCorrectReturnPointer(fiber, expectedReturnFiber) {
2071
if (__DEV__) {
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+155
-3
@@ -24,6 +24,7 @@ import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old';
24
import type {Wakeable} from 'shared/ReactTypes';
25
import type {ReactPriorityLevel} from './ReactInternalTypes';
26
import type {OffscreenState} from './ReactFiberOffscreenComponent';
27
+import type {HookFlags} from './ReactHookEffectTags';
28
29
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
30
import {
@@ -65,10 +66,13 @@ import {
66
NoFlags,
67
ContentReset,
68
Placement,
69
+ ChildDeletion,
70
Snapshot,
71
Update,
72
Passive,
73
+ PassiveStatic,
74
PassiveMask,
75
+ PassiveUnmountPendingDev,
76
} from './ReactFiberFlags';
77
import getComponentName from 'shared/getComponentName';
78
import invariant from 'shared/invariant';
@@ -340,19 +344,19 @@ function commitBeforeMutationLifeCycles(
344
);
345
}
346
343
-function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
347
+function commitHookEffectListUnmount(flags: HookFlags, finishedWork: Fiber) {
348
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
349
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
350
if (lastEffect !== null) {
351
const firstEffect = lastEffect.next;
352
let effect = firstEffect;
353
do {
350
- if ((effect.tag & tag) === tag) {
354
+ if ((effect.tag & flags) === flags) {
355
// Unmount
356
const destroy = effect.destroy;
357
effect.destroy = undefined;
358
if (destroy !== undefined) {
355
- destroy();
359
+ safelyCallDestroy(finishedWork, destroy);
360
}
361
}
362
effect = effect.next;
@@ -1914,6 +1918,154 @@ function commitPassiveMountOnFiber(
1918
}
1919
}
1920
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
+
2069
let didWarnWrongReturnPointer = false;
2070
function ensureCorrectReturnPointer(fiber, expectedReturnFiber) {
2071
if (__DEV__) {
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+26
-85
@@ -15,6 +15,7 @@ import type {Interaction} from 'scheduler/src/Tracing';
15
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
16
import type {Effect as HookEffect} from './ReactFiberHooks.new';
17
import type {StackCursor} from './ReactFiberStack.new';
18
+import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
19
20
import {
21
warnAboutDeprecatedLifecycles,
@@ -51,6 +52,10 @@ import {
52
flushSyncCallbackQueue,
53
scheduleSyncCallback,
54
} from './SchedulerWithReactIntegration.new';
55
+import {
56
+ NoFlags as NoHookEffect,
57
+ Passive as HookPassive,
58
+} from './ReactHookEffectTags';
59
import {
60
logCommitStarted,
61
logCommitStopped,
@@ -128,7 +133,7 @@ import {
133
Snapshot,
134
Callback,
135
Passive,
131
- PassiveUnmountPendingDev,
136
+ PassiveStatic,
137
Incomplete,
138
HostEffectMask,
139
Hydrating,
@@ -195,6 +200,7 @@ import {
200
commitResetTextContent,
201
isSuspenseBoundaryBeingHidden,
202
commitPassiveMountEffects,
203
+ commitPassiveUnmountEffects,
204
detachFiberAfterEffects,
205
} from './ReactFiberCommitWork.new';
206
import {enqueueUpdate} from './ReactUpdateQueue.new';
@@ -214,9 +220,7 @@ import {
220
import {
221
markNestedUpdateScheduled,
222
recordCommitTime,
217
- recordPassiveEffectDuration,
223
resetNestedUpdateFlag,
219
- startPassiveEffectTimer,
224
startProfilerTimer,
225
stopProfilerTimerIfRunningAndRecordDelta,
226
syncNestedUpdateFlag,
@@ -342,7 +346,6 @@ let rootDoesHavePassiveEffects: boolean = false;
346
let rootWithPendingPassiveEffects: FiberRoot | null = null;
347
let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority;
348
let pendingPassiveEffectsLanes: Lanes = NoLanes;
345
-let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = [];
349
let pendingPassiveProfilerEffects: Array<Fiber> = [];
350
351
let rootsWithPendingDiscreteUpdates: Set<FiberRoot> | null = null;
@@ -2512,14 +2515,6 @@ export function enqueuePendingPassiveHookEffectUnmount(
2515
fiber: Fiber,
2516
effect: HookEffect,
2517
): 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
- }
2518
if (!rootDoesHavePassiveEffects) {
2519
rootDoesHavePassiveEffects = true;
2520
scheduleCallback(NormalSchedulerPriority, () => {
@@ -2562,74 +2557,7 @@ function flushPassiveEffectsImpl() {
2557
executionContext |= CommitContext;
2558
const prevInteractions = pushInteractions(root);
2559
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);
2561
commitPassiveMountEffects(root, root.current);
2562
2563
// TODO: Move to commitPassiveMountEffects
@@ -3017,12 +2945,25 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
2945
return;
2946
}
2947
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;
2954
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
+ }
2967
// We show the whole stack but dedupe on the top component's name because
2968
// the problematic code almost always lies inside that component.
2969
const componentName = getComponentName(fiber.type) || 'ReactComponent';
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+26
-85
@@ -15,6 +15,7 @@ import type {Interaction} from 'scheduler/src/Tracing';
15
import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
16
import type {Effect as HookEffect} from './ReactFiberHooks.old';
17
import type {StackCursor} from './ReactFiberStack.old';
18
+import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old';
19
20
import {
21
warnAboutDeprecatedLifecycles,
@@ -51,6 +52,10 @@ import {
52
flushSyncCallbackQueue,
53
scheduleSyncCallback,
54
} from './SchedulerWithReactIntegration.old';
55
+import {
56
+ NoFlags as NoHookEffect,
57
+ Passive as HookPassive,
58
+} from './ReactHookEffectTags';
59
import {
60
logCommitStarted,
61
logCommitStopped,
@@ -128,7 +133,7 @@ import {
133
Snapshot,
134
Callback,
135
Passive,
131
- PassiveUnmountPendingDev,
136
+ PassiveStatic,
137
Incomplete,
138
HostEffectMask,
139
Hydrating,
@@ -195,6 +200,7 @@ import {
200
commitResetTextContent,
201
isSuspenseBoundaryBeingHidden,
202
commitPassiveMountEffects,
203
+ commitPassiveUnmountEffects,
204
detachFiberAfterEffects,
205
} from './ReactFiberCommitWork.old';
206
import {enqueueUpdate} from './ReactUpdateQueue.old';
@@ -214,9 +220,7 @@ import {
220
import {
221
markNestedUpdateScheduled,
222
recordCommitTime,
217
- recordPassiveEffectDuration,
223
resetNestedUpdateFlag,
219
- startPassiveEffectTimer,
224
startProfilerTimer,
225
stopProfilerTimerIfRunningAndRecordDelta,
226
syncNestedUpdateFlag,
@@ -342,7 +346,6 @@ let rootDoesHavePassiveEffects: boolean = false;
346
let rootWithPendingPassiveEffects: FiberRoot | null = null;
347
let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority;
348
let pendingPassiveEffectsLanes: Lanes = NoLanes;
345
-let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = [];
349
let pendingPassiveProfilerEffects: Array<Fiber> = [];
350
351
let rootsWithPendingDiscreteUpdates: Set<FiberRoot> | null = null;
@@ -2512,14 +2515,6 @@ export function enqueuePendingPassiveHookEffectUnmount(
2515
fiber: Fiber,
2516
effect: HookEffect,
2517
): 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
- }
2518
if (!rootDoesHavePassiveEffects) {
2519
rootDoesHavePassiveEffects = true;
2520
scheduleCallback(NormalSchedulerPriority, () => {
@@ -2562,74 +2557,7 @@ function flushPassiveEffectsImpl() {
2557
executionContext |= CommitContext;
2558
const prevInteractions = pushInteractions(root);
2559
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);
2561
commitPassiveMountEffects(root, root.current);
2562
2563
// TODO: Move to commitPassiveMountEffects
@@ -3017,12 +2945,25 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
2945
return;
2946
}
2947
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;
2954
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
+ }
2967
// We show the whole stack but dedupe on the top component's name because
2968
// the problematic code almost always lies inside that component.
2969
const componentName = getComponentName(fiber.type) || 'ReactComponent';