Skip to content

Commit 1c6be34

Browse files
author
Brian Vaughn
committed
Cleaned up hooks effect tags
We previously used separate Mount* and Unmount* tags to track hooks work for each phase (snapshot, mutation, layout, and passive). This was somewhat complicated to trace through and there were man tag types we never even used (e.g. UnmountLayout, MountMutation, UnmountSnapshot). In addition to this, it left passive and layout hooks looking the same after renders without changed dependencies, which meant we were unable to reliably defer passive effect destroy functions until after the commit phase. This commit reduces the effect tag types to only include Layout and Passive and differentiates between work and no-work with an HasEffect flag.
1 parent f42277e commit 1c6be34

File tree

4 files changed

+65
-75
lines changed

4 files changed

+65
-75
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,9 @@ import {
114114
} from './ReactFiberWorkLoop';
115115
import {
116116
NoEffect as NoHookEffect,
117-
NoEffectPassiveUnmountFiber,
118-
UnmountSnapshot,
119-
UnmountMutation,
120-
MountMutation,
121-
UnmountLayout,
122-
MountLayout,
123-
UnmountPassive,
124-
MountPassive,
117+
HasEffect as HookHasEffect,
118+
Layout as HookLayout,
119+
Passive as HookPassive,
125120
} from './ReactHookEffectTags';
126121
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork';
127122
import {runWithPriority, NormalPriority} from './SchedulerWithReactIntegration';
@@ -253,7 +248,6 @@ function commitBeforeMutationLifeCycles(
253248
case ForwardRef:
254249
case SimpleMemoComponent:
255250
case Chunk: {
256-
commitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork);
257251
return;
258252
}
259253
case ClassComponent: {
@@ -342,15 +336,21 @@ function commitHookEffectList(
342336
const firstEffect = lastEffect.next;
343337
let effect = firstEffect;
344338
do {
345-
if ((effect.tag & unmountTag) !== NoHookEffect) {
339+
if (
340+
(effect.tag & HookHasEffect) !== NoHookEffect &&
341+
(effect.tag & unmountTag) !== NoHookEffect
342+
) {
346343
// Unmount
347344
const destroy = effect.destroy;
348345
effect.destroy = undefined;
349346
if (destroy !== undefined) {
350347
destroy();
351348
}
352349
}
353-
if ((effect.tag & mountTag) !== NoHookEffect) {
350+
if (
351+
(effect.tag & HookHasEffect) !== NoHookEffect &&
352+
(effect.tag & mountTag) !== NoHookEffect
353+
) {
354354
// Mount
355355
const create = effect.create;
356356
effect.destroy = create();
@@ -401,8 +401,11 @@ export function commitPassiveHookEffects(finishedWork: Fiber): void {
401401
case ForwardRef:
402402
case SimpleMemoComponent:
403403
case Chunk: {
404-
commitHookEffectList(UnmountPassive, NoHookEffect, finishedWork);
405-
commitHookEffectList(NoHookEffect, MountPassive, finishedWork);
404+
// TODO (#17945) We should call all passive destroy functions (for all fibers)
405+
// before calling any create functions. The current approach only serializes
406+
// these for a single fiber.
407+
commitHookEffectList(HookPassive, NoHookEffect, finishedWork);
408+
commitHookEffectList(NoHookEffect, HookPassive, finishedWork);
406409
break;
407410
}
408411
default:
@@ -422,7 +425,11 @@ function commitLifeCycles(
422425
case ForwardRef:
423426
case SimpleMemoComponent:
424427
case Chunk: {
425-
commitHookEffectList(UnmountLayout, MountLayout, finishedWork);
428+
// At this point layout effects have already been destroyed (during mutation phase).
429+
// This is done to prevent sibling component effects from interfering with each other,
430+
// e.g. a destroy function in one component should never override a ref set
431+
// by a create function in another component during the same commit.
432+
commitHookEffectList(NoHookEffect, HookLayout, finishedWork);
426433
return;
427434
}
428435
case ClassComponent: {
@@ -764,10 +771,7 @@ function commitUnmount(
764771
do {
765772
const {destroy, tag} = effect;
766773
if (destroy !== undefined) {
767-
if (
768-
(tag & UnmountPassive) !== NoHookEffect ||
769-
(tag & NoEffectPassiveUnmountFiber) !== NoHookEffect
770-
) {
774+
if ((tag & HookPassive) !== NoHookEffect) {
771775
enqueuePendingPassiveEffectDestroyFn(destroy);
772776
} else {
773777
safelyCallDestroy(current, destroy);
@@ -1306,11 +1310,12 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
13061310
case MemoComponent:
13071311
case SimpleMemoComponent:
13081312
case Chunk: {
1309-
// Note: We currently never use MountMutation, but useLayoutEffect uses UnmountMutation.
1310-
// This is to ensure ALL destroy fns are run before create fns,
1311-
// without requiring us to traverse the effects list an extra time during commit.
1312-
// This sequence prevents sibling destroy and create fns from interfering with each other.
1313-
commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
1313+
// Layout effects are destroyed during the mutation phase so that all
1314+
// destroy functions for all fibers are called before any create functions.
1315+
// This prevents sibling component effects from interfering with each other,
1316+
// e.g. a destroy function in one component should never override a ref set
1317+
// by a create function in another component during the same commit.
1318+
commitHookEffectList(HookLayout, NoHookEffect, finishedWork);
13141319
return;
13151320
}
13161321
case Profiler: {
@@ -1348,11 +1353,12 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
13481353
case MemoComponent:
13491354
case SimpleMemoComponent:
13501355
case Chunk: {
1351-
// Note: We currently never use MountMutation, but useLayoutEffect uses UnmountMutation.
1352-
// This is to ensure ALL destroy fns are run before create fns,
1353-
// without requiring us to traverse the effects list an extra time during commit.
1354-
// This sequence prevents sibling destroy and create fns from interfering with each other.
1355-
commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
1356+
// Layout effects are destroyed during the mutation phase so that all
1357+
// destroy functions for all fibers are called before any create functions.
1358+
// This prevents sibling component effects from interfering with each other,
1359+
// e.g. a destroy function in one component should never override a ref set
1360+
// by a create function in another component during the same commit.
1361+
commitHookEffectList(HookLayout, NoHookEffect, finishedWork);
13561362
return;
13571363
}
13581364
case ClassComponent: {

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,9 @@ import {
2828
Passive as PassiveEffect,
2929
} from 'shared/ReactSideEffectTags';
3030
import {
31-
NoEffect as NoHookEffect,
32-
NoEffectPassiveUnmountFiber,
33-
UnmountMutation,
34-
MountLayout,
35-
UnmountPassive,
36-
MountPassive,
31+
HasEffect as HookHasEffect,
32+
Layout as HookLayout,
33+
Passive as HookPassive,
3734
} from './ReactHookEffectTags';
3835
import {
3936
scheduleWork,
@@ -924,16 +921,15 @@ function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
924921
const hook = mountWorkInProgressHook();
925922
const nextDeps = deps === undefined ? null : deps;
926923
currentlyRenderingFiber.effectTag |= fiberEffectTag;
927-
hook.memoizedState = pushEffect(hookEffectTag, create, undefined, nextDeps);
924+
hook.memoizedState = pushEffect(
925+
HookHasEffect | hookEffectTag,
926+
create,
927+
undefined,
928+
nextDeps,
929+
);
928930
}
929931

930-
function updateEffectImpl(
931-
fiberEffectTag,
932-
hookEffectTag,
933-
noWorkUnmountFiberHookEffectTag,
934-
create,
935-
deps,
936-
): void {
932+
function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
937933
const hook = updateWorkInProgressHook();
938934
const nextDeps = deps === undefined ? null : deps;
939935
let destroy = undefined;
@@ -944,15 +940,20 @@ function updateEffectImpl(
944940
if (nextDeps !== null) {
945941
const prevDeps = prevEffect.deps;
946942
if (areHookInputsEqual(nextDeps, prevDeps)) {
947-
pushEffect(noWorkUnmountFiberHookEffectTag, create, destroy, nextDeps);
943+
pushEffect(hookEffectTag, create, destroy, nextDeps);
948944
return;
949945
}
950946
}
951947
}
952948

953949
currentlyRenderingFiber.effectTag |= fiberEffectTag;
954950

955-
hook.memoizedState = pushEffect(hookEffectTag, create, destroy, nextDeps);
951+
hook.memoizedState = pushEffect(
952+
HookHasEffect | hookEffectTag,
953+
create,
954+
destroy,
955+
nextDeps,
956+
);
956957
}
957958

958959
function mountEffect(
@@ -967,7 +968,7 @@ function mountEffect(
967968
}
968969
return mountEffectImpl(
969970
UpdateEffect | PassiveEffect,
970-
UnmountPassive | MountPassive,
971+
HookPassive,
971972
create,
972973
deps,
973974
);
@@ -985,8 +986,7 @@ function updateEffect(
985986
}
986987
return updateEffectImpl(
987988
UpdateEffect | PassiveEffect,
988-
UnmountPassive | MountPassive,
989-
NoEffectPassiveUnmountFiber,
989+
HookPassive,
990990
create,
991991
deps,
992992
);
@@ -996,27 +996,14 @@ function mountLayoutEffect(
996996
create: () => (() => void) | void,
997997
deps: Array<mixed> | void | null,
998998
): void {
999-
return mountEffectImpl(
1000-
UpdateEffect,
1001-
UnmountMutation | MountLayout,
1002-
create,
1003-
deps,
1004-
);
999+
return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
10051000
}
10061001

10071002
function updateLayoutEffect(
10081003
create: () => (() => void) | void,
10091004
deps: Array<mixed> | void | null,
10101005
): void {
1011-
// Layout effects use UnmountMutation to ensure all destroy fns are run before create fns.
1012-
// This optimization lets us avoid traversing the effects list an extra time during the layout phase.
1013-
return updateEffectImpl(
1014-
UpdateEffect,
1015-
UnmountMutation | MountLayout,
1016-
NoHookEffect,
1017-
create,
1018-
deps,
1019-
);
1006+
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
10201007
}
10211008

10221009
function imperativeHandleEffect<T>(
@@ -1070,7 +1057,7 @@ function mountImperativeHandle<T>(
10701057

10711058
return mountEffectImpl(
10721059
UpdateEffect,
1073-
UnmountMutation | MountLayout,
1060+
HookLayout,
10741061
imperativeHandleEffect.bind(null, create, ref),
10751062
effectDeps,
10761063
);
@@ -1097,8 +1084,7 @@ function updateImperativeHandle<T>(
10971084

10981085
return updateEffectImpl(
10991086
UpdateEffect,
1100-
UnmountMutation | MountLayout,
1101-
NoHookEffect,
1087+
HookLayout,
11021088
imperativeHandleEffect.bind(null, create, ref),
11031089
effectDeps,
11041090
);

packages/react-reconciler/src/ReactHookEffectTags.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@
99

1010
export type HookEffectTag = number;
1111

12-
export const NoEffect = /* */ 0b000000000;
13-
export const UnmountSnapshot = /* */ 0b000000010;
14-
export const UnmountMutation = /* */ 0b000000100;
15-
export const MountMutation = /* */ 0b000001000;
16-
export const UnmountLayout = /* */ 0b000010000;
17-
export const MountLayout = /* */ 0b000100000;
18-
export const MountPassive = /* */ 0b001000000;
19-
export const UnmountPassive = /* */ 0b010000000;
20-
export const NoEffectPassiveUnmountFiber = /**/ 0b100000000;
12+
export const NoEffect = /* */ 0b000;
13+
14+
// Represents whether effect should fire.
15+
export const HasEffect = /* */ 0b001;
16+
17+
// Represents the phase in which the effect (not the clean-up) fires.
18+
export const Layout = /* */ 0b010;
19+
export const Passive = /* */ 0b100;

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1022,7 +1022,6 @@ describe('ReactHooksWithNoopRenderer', () => {
10221022

10231023
// This update is exists to test an internal implementation detail:
10241024
// Effects without updating dependencies lose their layout/passive tag during an update.
1025-
// A special type of no-update tag (NoEffectPassiveUnmountFiber) is used to track these for later.
10261025
act(() => {
10271026
ReactNoop.render(<Child bar={1} foo={2} />, () =>
10281027
Scheduler.unstable_yieldValue('Sync effect'),

0 commit comments

Comments
 (0)