26
27
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
28
import {
29
+ deferPassiveEffectCleanupDuringUnmount,
30
enableSchedulerTracing,
31
enableProfilerTimer,
32
enableSuspenseServerRenderer,
110
captureCommitPhaseError,
111
resolveRetryThenable,
112
markCommitTimeOfFallback,
113
+ enqueuePendingPassiveEffectDestroyFn,
114
} from './ReactFiberWorkLoop';
115
import {
116
NoEffect as NoHookEffect,
115
- UnmountSnapshot,
116
- UnmountMutation,
117
- MountMutation,
118
- UnmountLayout,
119
- MountLayout,
120
- UnmountPassive,
121
- MountPassive,
117
+ HasEffect as HookHasEffect,
118
+ Layout as HookLayout,
119
+ Passive as HookPassive,
120
} from './ReactHookEffectTags';
121
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork';
122
import {runWithPriority, NormalPriority} from './SchedulerWithReactIntegration';
248
case ForwardRef:
249
case SimpleMemoComponent:
250
case Chunk: {
253
- commitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork);
251
return;
252
}
253
case ClassComponent: {
325
);
326
}
327
331
-function commitHookEffectList(
332
- unmountTag: number,
333
- mountTag: number,
334
- finishedWork: Fiber,
335
-) {
328
+function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
329
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
330
let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
331
if (lastEffect !== null) {
332
const firstEffect = lastEffect.next;
333
let effect = firstEffect;
334
do {
342
- if ((effect.tag & unmountTag) !== NoHookEffect) {
335
+ if ((effect.tag & tag) === tag) {
336
// Unmount
337
const destroy = effect.destroy;
338
effect.destroy = undefined;
340
destroy();
341
}
342
}
350
- if ((effect.tag & mountTag) !== NoHookEffect) {
343
+ effect = effect.next;
344
+ } while (effect !== firstEffect);
345
+ }
346
+}
347
+
348
+function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
349
+ const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
350
+ let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
351
+ if (lastEffect !== null) {
352
+ const firstEffect = lastEffect.next;
353
+ let effect = firstEffect;
354
+ do {
355
+ if ((effect.tag & tag) === tag) {
356
// Mount
357
const create = effect.create;
358
effect.destroy = create();
403
case ForwardRef:
404
case SimpleMemoComponent:
405
case Chunk: {
401
- commitHookEffectList(UnmountPassive, NoHookEffect, finishedWork);
402
- commitHookEffectList(NoHookEffect, MountPassive, finishedWork);
406
+ // TODO (#17945) We should call all passive destroy functions (for all fibers)
407
+ // before calling any create functions. The current approach only serializes
408
+ // these for a single fiber.
409
+ commitHookEffectListUnmount(HookPassive | HookHasEffect, finishedWork);
410
+ commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
411
break;
412
}
413
default:
427
case ForwardRef:
428
case SimpleMemoComponent:
429
case Chunk: {
422
- commitHookEffectList(UnmountLayout, MountLayout, finishedWork);
430
+ // At this point layout effects have already been destroyed (during mutation phase).
431
+ // This is done to prevent sibling component effects from interfering with each other,
432
+ // e.g. a destroy function in one component should never override a ref set
433
+ // by a create function in another component during the same commit.
434
+ commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
435
return;
436
}
437
case ClassComponent: {
768
if (lastEffect !== null) {
769
const firstEffect = lastEffect.next;
770
759
- // When the owner fiber is deleted, the destroy function of a passive
760
- // effect hook is called during the synchronous commit phase. This is
761
- // a concession to implementation complexity. Calling it in the
762
- // passive effect phase (like they usually are, when dependencies
763
- // change during an update) would require either traversing the
764
- // children of the deleted fiber again, or including unmount effects
765
- // as part of the fiber effect list.
766
- //
767
- // Because this is during the sync commit phase, we need to change
768
- // the priority.
769
- //
770
- // TODO: Reconsider this implementation trade off.
771
- const priorityLevel =
772
- renderPriorityLevel > NormalPriority
773
- ? NormalPriority
774
- : renderPriorityLevel;
775
- runWithPriority(priorityLevel, () => {
771
+ if (deferPassiveEffectCleanupDuringUnmount) {
772
let effect = firstEffect;
773
do {
778
- const destroy = effect.destroy;
774
+ const {destroy, tag} = effect;
775
if (destroy !== undefined) {
780
- safelyCallDestroy(current, destroy);
776
+ if ((tag & HookPassive) !== NoHookEffect) {
777
+ enqueuePendingPassiveEffectDestroyFn(destroy);
778
+ } else {
779
+ safelyCallDestroy(current, destroy);
780
+ }
781
}
782
effect = effect.next;
783
} while (effect !== firstEffect);
784
- });
784
+ } else {
785
+ // When the owner fiber is deleted, the destroy function of a passive
786
+ // effect hook is called during the synchronous commit phase. This is
787
+ // a concession to implementation complexity. Calling it in the
788
+ // passive effect phase (like they usually are, when dependencies
789
+ // change during an update) would require either traversing the
790
+ // children of the deleted fiber again, or including unmount effects
791
+ // as part of the fiber effect list.
792
+ //
793
+ // Because this is during the sync commit phase, we need to change
794
+ // the priority.
795
+ //
796
+ // TODO: Reconsider this implementation trade off.
797
+ const priorityLevel =
798
+ renderPriorityLevel > NormalPriority
799
+ ? NormalPriority
800
+ : renderPriorityLevel;
801
+ runWithPriority(priorityLevel, () => {
802
+ let effect = firstEffect;
803
+ do {
804
+ const destroy = effect.destroy;
805
+ if (destroy !== undefined) {
806
+ safelyCallDestroy(current, destroy);
807
+ }
808
+ effect = effect.next;
809
+ } while (effect !== firstEffect);
810
+ });
811
+ }
812
}
813
}
814
return;
1312
case MemoComponent:
1313
case SimpleMemoComponent:
1314
case Chunk: {
1288
- // Note: We currently never use MountMutation, but useLayout uses
1289
- // UnmountMutation.
1290
- commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
1315
+ // Layout effects are destroyed during the mutation phase so that all
1316
+ // destroy functions for all fibers are called before any create functions.
1317
+ // This prevents sibling component effects from interfering with each other,
1318
+ // e.g. a destroy function in one component should never override a ref set
1319
+ // by a create function in another component during the same commit.
1320
+ commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
1321
return;
1322
}
1323
case Profiler: {
1355
case MemoComponent:
1356
case SimpleMemoComponent:
1357
case Chunk: {
1328
- // Note: We currently never use MountMutation, but useLayout uses
1329
- // UnmountMutation.
1330
- commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
1358
+ // Layout effects are destroyed during the mutation phase so that all
1359
+ // destroy functions for all fibers are called before any create functions.
1360
+ // This prevents sibling component effects from interfering with each other,
1361
+ // e.g. a destroy function in one component should never override a ref set
1362
+ // by a create function in another component during the same commit.
1363
+ commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
1364
return;
1365
}
1366
case ClassComponent: {