Skip to content

Commit 5b6bc9d

Browse files
author
Brian Vaughn
committed
Add static effectTag bit for passive effect cleanup after unmount
1 parent c45bc43 commit 5b6bc9d

8 files changed

+117
-42
lines changed

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,16 @@ import type {Fiber} from './ReactInternalTypes';
1515
import type {Lanes} from './ReactFiberLane';
1616

1717
import getComponentName from 'shared/getComponentName';
18-
import {Placement, Deletion} from './ReactSideEffectTags';
18+
import {
19+
Deletion,
20+
NoEffect,
21+
PassiveMask,
22+
Placement,
23+
} from './ReactSideEffectTags';
24+
import {
25+
NoEffect as NoSubtreeTag,
26+
Passive as PassiveSubtreeTag,
27+
} from './ReactSubtreeTags';
1928
import {
2029
getIteratorFn,
2130
REACT_ELEMENT_TYPE,
@@ -293,6 +302,14 @@ function ChildReconciler(shouldTrackSideEffects) {
293302
returnFiber.deletions = [childToDelete];
294303
// TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
295304
returnFiber.effectTag |= Deletion;
305+
306+
// If we are deleting a subtree that contains a passive effect,
307+
// mark the parent so that we're sure to traverse after commit and run any unmount functions.
308+
const primaryEffectTag = childToDelete.effectTag & PassiveMask;
309+
const primarySubtreeTag = childToDelete.subtreeTag & PassiveSubtreeTag;
310+
if (primaryEffectTag !== NoEffect || primarySubtreeTag !== NoSubtreeTag) {
311+
returnFiber.subtreeTag |= PassiveSubtreeTag;
312+
}
296313
} else {
297314
deletions.push(childToDelete);
298315
}

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,8 @@ import {
2929
enableScopeAPI,
3030
enableBlocksAPI,
3131
} from 'shared/ReactFeatureFlags';
32-
import {NoEffect, Placement} from './ReactSideEffectTags';
33-
import {
34-
NoEffect as NoSubtreeEffect,
35-
Static as StaticSubtreeEffects,
36-
} from './ReactSubtreeTags';
32+
import {NoEffect, Placement, StaticMask} from './ReactSideEffectTags';
33+
import {NoEffect as NoSubtreeEffect} from './ReactSubtreeTags';
3734
import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
3835
import {
3936
IndeterminateComponent,
@@ -310,7 +307,11 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
310307
}
311308
}
312309

313-
workInProgress.subtreeTag = current.subtreeTag & StaticSubtreeEffects;
310+
// Reset all effects except static ones.
311+
// Static effects are not specific to a render.
312+
workInProgress.effectTag = current.effectTag & StaticMask;
313+
314+
workInProgress.subtreeTag = NoSubtreeEffect;
314315
workInProgress.childLanes = current.childLanes;
315316
workInProgress.lanes = current.lanes;
316317

@@ -375,7 +376,8 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
375376

376377
// Reset the effect tag but keep any Placement tags, since that's something
377378
// that child fiber is setting, not the reconciliation.
378-
workInProgress.effectTag &= Placement;
379+
// Also keep static tags since those are meant to last the life of the fiber.
380+
workInProgress.effectTag &= Placement | StaticMask;
379381

380382
// The effect list is no longer valid.
381383
workInProgress.nextEffect = null;

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,13 @@ import {
6464
Ref,
6565
Deletion,
6666
ForceUpdateForLegacySuspense,
67+
PassiveMask,
68+
StaticMask,
6769
} from './ReactSideEffectTags';
70+
import {
71+
NoEffect as NoSubtreeTag,
72+
Passive as PassiveSubtreeTag,
73+
} from './ReactSubtreeTags';
6874
import ReactSharedInternals from 'shared/ReactSharedInternals';
6975
import {
7076
debugRenderPhaseSideEffectsForStrictMode,
@@ -2064,13 +2070,24 @@ function updateSuspensePrimaryChildren(
20642070
if (currentFallbackChildFragment !== null) {
20652071
// Delete the fallback child fragment
20662072
currentFallbackChildFragment.nextEffect = null;
2067-
currentFallbackChildFragment.effectTag = Deletion;
2073+
currentFallbackChildFragment.effectTag =
2074+
(currentFallbackChildFragment.effectTag & StaticMask) | Deletion;
20682075
workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChildFragment;
20692076
const deletions = workInProgress.deletions;
20702077
if (deletions === null) {
20712078
workInProgress.deletions = [currentFallbackChildFragment];
20722079
// TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
20732080
workInProgress.effectTag |= Deletion;
2081+
2082+
// If we are deleting a subtree that contains a passive effect,
2083+
// mark the parent so that we're sure to traverse after commit and run any unmount functions.
2084+
const primaryEffectTag =
2085+
currentFallbackChildFragment.effectTag & PassiveMask;
2086+
const primarySubtreeTag =
2087+
currentFallbackChildFragment.subtreeTag & PassiveSubtreeTag;
2088+
if (primaryEffectTag !== NoEffect || primarySubtreeTag !== NoSubtreeTag) {
2089+
workInProgress.subtreeTag |= PassiveSubtreeTag;
2090+
}
20742091
} else {
20752092
deletions.push(currentFallbackChildFragment);
20762093
}
@@ -3055,6 +3072,14 @@ function remountFiber(
30553072
returnFiber.deletions = [current];
30563073
// TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
30573074
returnFiber.effectTag |= Deletion;
3075+
3076+
// If we are deleting a subtree that contains a passive effect,
3077+
// mark the parent so that we're sure to traverse after commit and run any unmount functions.
3078+
const primaryEffectTag = current.effectTag & PassiveMask;
3079+
const primarySubtreeTag = current.subtreeTag & PassiveSubtreeTag;
3080+
if (primaryEffectTag !== NoEffect || primarySubtreeTag !== NoSubtreeTag) {
3081+
returnFiber.subtreeTag |= PassiveSubtreeTag;
3082+
}
30583083
} else {
30593084
deletions.push(current);
30603085
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {createDeprecatedResponderListener} from './ReactFiberDeprecatedEvents.ne
5050
import {
5151
Update as UpdateEffect,
5252
Passive as PassiveEffect,
53+
PassiveStatic as PassiveStaticEffect,
5354
} from './ReactSideEffectTags';
5455
import {
5556
HasEffect as HookHasEffect,
@@ -1270,7 +1271,7 @@ function mountEffect(
12701271
}
12711272
}
12721273
return mountEffectImpl(
1273-
UpdateEffect | PassiveEffect,
1274+
UpdateEffect | PassiveEffect | PassiveStaticEffect,
12741275
HookPassive,
12751276
create,
12761277
deps,
@@ -1288,7 +1289,7 @@ function updateEffect(
12881289
}
12891290
}
12901291
return updateEffectImpl(
1291-
UpdateEffect | PassiveEffect,
1292+
UpdateEffect | PassiveEffect | PassiveStaticEffect,
12921293
HookPassive,
12931294
create,
12941295
deps,
@@ -1631,7 +1632,8 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
16311632
const setId = mountState(id)[1];
16321633

16331634
if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
1634-
currentlyRenderingFiber.effectTag |= UpdateEffect | PassiveEffect;
1635+
currentlyRenderingFiber.effectTag |=
1636+
UpdateEffect | PassiveEffect | PassiveStaticEffect;
16351637
pushEffect(
16361638
HookHasEffect | HookPassive,
16371639
() => {

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,17 @@ import {
2424
HostRoot,
2525
SuspenseComponent,
2626
} from './ReactWorkTags';
27-
import {Deletion, Hydrating, Placement} from './ReactSideEffectTags';
27+
import {
28+
Deletion,
29+
Hydrating,
30+
NoEffect,
31+
PassiveMask,
32+
Placement,
33+
} from './ReactSideEffectTags';
34+
import {
35+
NoEffect as NoSubtreeTag,
36+
Passive as PassiveSubtreeTag,
37+
} from './ReactSubtreeTags';
2838
import invariant from 'shared/invariant';
2939

3040
import {
@@ -130,6 +140,14 @@ function deleteHydratableInstance(
130140
returnFiber.deletions = [childToDelete];
131141
// TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
132142
returnFiber.effectTag |= Deletion;
143+
144+
// If we are deleting a subtree that contains a passive effect,
145+
// mark the parent so that we're sure to traverse after commit and run any unmount functions.
146+
const primaryEffectTag = childToDelete.effectTag & PassiveMask;
147+
const primarySubtreeTag = childToDelete.subtreeTag & PassiveSubtreeTag;
148+
if (primaryEffectTag !== NoEffect || primarySubtreeTag !== NoSubtreeTag) {
149+
returnFiber.subtreeTag |= PassiveSubtreeTag;
150+
}
133151
} else {
134152
deletions.push(childToDelete);
135153
}

packages/react-reconciler/src/ReactSideEffectTags.js

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,49 @@
1010
export type SideEffectTag = number;
1111

1212
// Don't change these two values. They're used by React Dev Tools.
13-
export const NoEffect = /* */ 0b000000000000000;
14-
export const PerformedWork = /* */ 0b000000000000001;
13+
export const NoEffect = /* */ 0b0000000000000000;
14+
export const PerformedWork = /* */ 0b0000000000000001;
1515

1616
// You can change the rest (and add more).
17-
export const Placement = /* */ 0b000000000000010;
18-
export const Update = /* */ 0b000000000000100;
19-
export const PlacementAndUpdate = /* */ 0b000000000000110;
20-
export const Deletion = /* */ 0b000000000001000;
21-
export const ContentReset = /* */ 0b000000000010000;
22-
export const Callback = /* */ 0b000000000100000;
23-
export const DidCapture = /* */ 0b000000001000000;
24-
export const Ref = /* */ 0b000000010000000;
25-
export const Snapshot = /* */ 0b000000100000000;
26-
export const Passive = /* */ 0b000001000000000;
27-
export const PassiveUnmountPendingDev = /* */ 0b010000000000000;
28-
export const Hydrating = /* */ 0b000010000000000;
29-
export const HydratingAndUpdate = /* */ 0b000010000000100;
17+
export const Placement = /* */ 0b0000000000000010;
18+
export const Update = /* */ 0b0000000000000100;
19+
export const PlacementAndUpdate = /* */ 0b0000000000000110;
20+
export const Deletion = /* */ 0b0000000000001000;
21+
export const ContentReset = /* */ 0b0000000000010000;
22+
export const Callback = /* */ 0b0000000000100000;
23+
export const DidCapture = /* */ 0b0000000001000000;
24+
export const Ref = /* */ 0b0000000010000000;
25+
export const Snapshot = /* */ 0b0000000100000000;
26+
export const Passive = /* */ 0b0000001000000000;
27+
export const PassiveUnmountPendingDev = /* */ 0b0010000000000000;
28+
export const Hydrating = /* */ 0b0000010000000000;
29+
export const HydratingAndUpdate = /* */ 0b0000010000000100;
3030

3131
// Passive & Update & Callback & Ref & Snapshot
32-
export const LifecycleEffectMask = /* */ 0b000001110100100;
32+
export const LifecycleEffectMask = /* */ 0b0000001110100100;
3333

3434
// Union of all host effects
35-
export const HostEffectMask = /* */ 0b000011111111111;
35+
export const HostEffectMask = /* */ 0b0000011111111111;
3636

3737
// These are not really side effects, but we still reuse this field.
38-
export const Incomplete = /* */ 0b000100000000000;
39-
export const ShouldCapture = /* */ 0b001000000000000;
40-
export const ForceUpdateForLegacySuspense = /* */ 0b100000000000000;
38+
export const Incomplete = /* */ 0b0000100000000000;
39+
export const ShouldCapture = /* */ 0b0001000000000000;
40+
export const ForceUpdateForLegacySuspense = /* */ 0b0100000000000000;
41+
42+
// Static tags describe aspects of a fiber that are not specific to a render,
43+
// e.g. a fiber uses a passive effect (even if there are no updates on this particular render).
44+
// This enables us to defer more work in the unmount case,
45+
// since we can defer traversing the tree during layout to look for Passive effects,
46+
// and instead rely on the static flag as a signal that there may be cleanup work.
47+
export const PassiveStatic = /* */ 0b1000000000000000;
4148

4249
// Union of side effect groupings as pertains to subtreeTag
43-
export const BeforeMutationMask = /* */ 0b000001100001010;
44-
export const MutationMask = /* */ 0b000010010011110;
45-
export const LayoutMask = /* */ 0b000000010100100;
46-
export const PassiveMask = /* */ 0b000001000000000;
50+
export const BeforeMutationMask = /* */ 0b0000001100001010;
51+
export const MutationMask = /* */ 0b0000010010011110;
52+
export const LayoutMask = /* */ 0b0000000010100100;
53+
export const PassiveMask = /* */ 0b1000001000000000;
54+
55+
// Union of tags that don't get reset on clones.
56+
// This allows certain concepts to persist without recalculting them,
57+
// e.g. whether a subtree contains passive effects or portals.
58+
export const StaticMask = /* */ 0b1000000000000000;

packages/react-reconciler/src/ReactSubtreeTags.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,3 @@ export const BeforeMutation = /* */ 0b0001;
1414
export const Mutation = /* */ 0b0010;
1515
export const Layout = /* */ 0b0100;
1616
export const Passive = /* */ 0b1000;
17-
18-
// Union of tags that don't get reset on clones.
19-
// This allows certain concepts to persist without recalculting them,
20-
// e.g. whether a subtree contains passive effects or portals.
21-
export const Static = /* */ 0b1000;

packages/react-reconciler/src/__tests__/SchedulingProfiler-test.internal.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,11 @@ describe('SchedulingProfiler', () => {
502502
'--render-start-1024',
503503
'--render-stop',
504504
'--commit-start-1024',
505+
'--layout-effects-start-1024',
506+
'--layout-effects-stop',
505507
'--commit-stop',
508+
'--passive-effects-start-1024',
509+
'--passive-effects-stop',
506510
]);
507511
});
508512

0 commit comments

Comments
 (0)