@samitouri / QOS-React-2 / commits / 1cf59f34b8

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';