Traverse commit phase effects iteratively (#20094)
* Move traversal logic to ReactFiberCommitWork The current traversal logic is spread between ReactFiberWorkLoop and ReactFiberCommitWork, and it's a bit awkward, especially when refactoring. Idk the ideal module structure, so for now I'd rather keep it all in one file. * Traverse commit phase effects iteratively We suspect that using the JS stack to traverse through the tree in the commit phase is slower than traversing iteratively. I've kept the recursive implementation behind a flag, both so we have the option to run an experiment comparing the two, and so we can revert it easily later if needed.
Andrew Clark committed
Oct 27, 2020 at 14:02 UTC
25b18d31c8b7ffd256416672dc21c50dd22ac3dc
11 files changed
+1255
-623
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+1224
-126
@@ -24,6 +24,7 @@ import type {Wakeable} from 'shared/ReactTypes';
24
import type {ReactPriorityLevel} from './ReactInternalTypes';
25
import type {OffscreenState} from './ReactFiberOffscreenComponent';
26
import type {HookFlags} from './ReactHookEffectTags';
27
+import type {Flags} from './ReactFiberFlags';
28
29
import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
30
import {
@@ -35,6 +36,7 @@ import {
36
enableSuspenseCallback,
37
enableScopeAPI,
38
enableDoubleInvokingEffects,
39
+ enableRecursiveCommitTraversal,
40
} from 'shared/ReactFeatureFlags';
41
import {
42
FunctionComponent,
@@ -67,10 +69,21 @@ import {
69
ContentReset,
70
Placement,
71
Snapshot,
72
+ Visibility,
73
Update,
74
Callback,
72
- LayoutMask,
75
Ref,
76
+ PlacementAndUpdate,
77
+ Hydrating,
78
+ HydratingAndUpdate,
79
+ Passive,
80
+ PassiveStatic,
81
+ BeforeMutationMask,
82
+ MutationMask,
83
+ LayoutMask,
84
+ PassiveMask,
85
+ MountLayoutDev,
86
+ MountPassiveDev,
87
} from './ReactFiberFlags';
88
import getComponentName from 'shared/getComponentName';
89
import invariant from 'shared/invariant';
@@ -88,13 +101,20 @@ import {
101
recordPassiveEffectDuration,
102
startPassiveEffectTimer,
103
} from './ReactProfilerTimer.new';
91
-import {ProfileMode} from './ReactTypeOfMode';
104
+import {
105
+ NoMode,
106
+ BlockingMode,
107
+ ConcurrentMode,
108
+ ProfileMode,
109
+} from './ReactTypeOfMode';
110
import {commitUpdateQueue} from './ReactUpdateQueue.new';
111
import {
112
getPublicInstance,
113
supportsMutation,
114
supportsPersistence,
115
supportsHydration,
116
+ prepareForCommit,
117
+ beforeActiveInstanceBlur,
118
commitMount,
119
commitUpdate,
120
resetTextContent,
@@ -132,6 +152,9 @@ import {
152
Passive as HookPassive,
153
} from './ReactHookEffectTags';
154
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
155
+import {doesFiberContain} from './ReactFiberTreeReflection';
156
+
157
+let nextEffect: Fiber | null = null;
158
159
// Used to avoid traversing the return path to find the nearest Profiler ancestor during commit.
160
let nearestProfilerOnStack: Fiber | null = null;
@@ -256,98 +279,6 @@ export function safelyCallDestroy(
279
}
280
}
281
259
-function commitBeforeMutationLifeCycles(
260
- current: Fiber | null,
261
- finishedWork: Fiber,
262
-): void {
263
- switch (finishedWork.tag) {
264
- case FunctionComponent:
265
- case ForwardRef:
266
- case SimpleMemoComponent:
267
- case Block: {
268
- return;
269
- }
270
- case ClassComponent: {
271
- if (finishedWork.flags & Snapshot) {
272
- if (current !== null) {
273
- const prevProps = current.memoizedProps;
274
- const prevState = current.memoizedState;
275
- const instance = finishedWork.stateNode;
276
- // We could update instance props and state here,
277
- // but instead we rely on them being set during last render.
278
- // TODO: revisit this when we implement resuming.
279
- if (__DEV__) {
280
- if (
281
- finishedWork.type === finishedWork.elementType &&
282
- !didWarnAboutReassigningProps
283
- ) {
284
- if (instance.props !== finishedWork.memoizedProps) {
285
- console.error(
286
- 'Expected %s props to match memoized props before ' +
287
- 'getSnapshotBeforeUpdate. ' +
288
- 'This might either be because of a bug in React, or because ' +
289
- 'a component reassigns its own `this.props`. ' +
290
- 'Please file an issue.',
291
- getComponentName(finishedWork.type) || 'instance',
292
- );
293
- }
294
- if (instance.state !== finishedWork.memoizedState) {
295
- console.error(
296
- 'Expected %s state to match memoized state before ' +
297
- 'getSnapshotBeforeUpdate. ' +
298
- 'This might either be because of a bug in React, or because ' +
299
- 'a component reassigns its own `this.state`. ' +
300
- 'Please file an issue.',
301
- getComponentName(finishedWork.type) || 'instance',
302
- );
303
- }
304
- }
305
- }
306
- const snapshot = instance.getSnapshotBeforeUpdate(
307
- finishedWork.elementType === finishedWork.type
308
- ? prevProps
309
- : resolveDefaultProps(finishedWork.type, prevProps),
310
- prevState,
311
- );
312
- if (__DEV__) {
313
- const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
314
- if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
315
- didWarnSet.add(finishedWork.type);
316
- console.error(
317
- '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
318
- 'must be returned. You have returned undefined.',
319
- getComponentName(finishedWork.type),
320
- );
321
- }
322
- }
323
- instance.__reactInternalSnapshotBeforeUpdate = snapshot;
324
- }
325
- }
326
- return;
327
- }
328
- case HostRoot: {
329
- if (supportsMutation) {
330
- if (finishedWork.flags & Snapshot) {
331
- const root = finishedWork.stateNode;
332
- clearContainer(root.containerInfo);
333
- }
334
- }
335
- return;
336
- }
337
- case HostComponent:
338
- case HostText:
339
- case HostPortal:
340
- case IncompleteClassComponent:
341
- // Nothing to do for these component types
342
- return;
343
- }
344
- invariant(
345
- false,
346
- 'This unit of work tag should not have side-effects. This error is ' +
347
- 'likely caused by a bug in React. Please file an issue.',
348
- );
349
-}
350
-
282
function commitHookEffectListUnmount(
283
flags: HookFlags,
284
finishedWork: Fiber,
@@ -462,6 +393,530 @@ function commitProfilerPassiveEffect(
393
}
394
}
395
396
+let focusedInstanceHandle: null | Fiber = null;
397
+let shouldFireAfterActiveInstanceBlur: boolean = false;
398
+
399
+export function commitBeforeMutationEffects(
400
+ root: FiberRoot,
401
+ firstChild: Fiber,
402
+) {
403
+ focusedInstanceHandle = prepareForCommit(root.containerInfo);
404
+
405
+ if (enableRecursiveCommitTraversal) {
406
+ recursivelyCommitBeforeMutationEffects(firstChild);
407
+ } else {
408
+ nextEffect = firstChild;
409
+ iterativelyCommitBeforeMutationEffects_begin();
410
+ }
411
+
412
+ // We no longer need to track the active instance fiber
413
+ const shouldFire = shouldFireAfterActiveInstanceBlur;
414
+ shouldFireAfterActiveInstanceBlur = false;
415
+ focusedInstanceHandle = null;
416
+
417
+ return shouldFire;
418
+}
419
+
420
+function recursivelyCommitBeforeMutationEffects(firstChild: Fiber) {
421
+ let fiber = firstChild;
422
+ while (fiber !== null) {
423
+ // TODO: Should wrap this in flags check, too, as optimization
424
+ if (fiber.deletions !== null) {
425
+ commitBeforeMutationEffectsDeletions(fiber.deletions);
426
+ }
427
+
428
+ const child = fiber.child;
429
+ if (fiber.subtreeFlags & BeforeMutationMask && child !== null) {
430
+ recursivelyCommitBeforeMutationEffects(child);
431
+ }
432
+
433
+ if (__DEV__) {
434
+ setCurrentDebugFiberInDEV(fiber);
435
+ invokeGuardedCallback(
436
+ null,
437
+ commitBeforeMutationEffectsOnFiber,
438
+ null,
439
+ fiber,
440
+ );
441
+ if (hasCaughtError()) {
442
+ const error = clearCaughtError();
443
+ captureCommitPhaseError(fiber, fiber.return, error);
444
+ }
445
+ resetCurrentDebugFiberInDEV();
446
+ } else {
447
+ try {
448
+ commitBeforeMutationEffectsOnFiber(fiber);
449
+ } catch (error) {
450
+ captureCommitPhaseError(fiber, fiber.return, error);
451
+ }
452
+ }
453
+ fiber = fiber.sibling;
454
+ }
455
+}
456
+
457
+function iterativelyCommitBeforeMutationEffects_begin() {
458
+ while (nextEffect !== null) {
459
+ const fiber = nextEffect;
460
+
461
+ // TODO: Should wrap this in flags check, too, as optimization
462
+ const deletions = fiber.deletions;
463
+ if (deletions !== null) {
464
+ commitBeforeMutationEffectsDeletions(deletions);
465
+ }
466
+
467
+ const child = fiber.child;
468
+ if (
469
+ (fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
470
+ child !== null
471
+ ) {
472
+ child.return = fiber;
473
+ nextEffect = child;
474
+ } else {
475
+ iterativelyCommitBeforeMutationEffects_complete();
476
+ }
477
+ }
478
+}
479
+
480
+function iterativelyCommitBeforeMutationEffects_complete() {
481
+ while (nextEffect !== null) {
482
+ const fiber = nextEffect;
483
+ if (__DEV__) {
484
+ setCurrentDebugFiberInDEV(fiber);
485
+ invokeGuardedCallback(
486
+ null,
487
+ commitBeforeMutationEffectsOnFiber,
488
+ null,
489
+ fiber,
490
+ );
491
+ if (hasCaughtError()) {
492
+ const error = clearCaughtError();
493
+ captureCommitPhaseError(fiber, fiber.return, error);
494
+ }
495
+ resetCurrentDebugFiberInDEV();
496
+ } else {
497
+ try {
498
+ commitBeforeMutationEffectsOnFiber(fiber);
499
+ } catch (error) {
500
+ captureCommitPhaseError(fiber, fiber.return, error);
501
+ }
502
+ }
503
+
504
+ const sibling = fiber.sibling;
505
+ if (sibling !== null) {
506
+ sibling.return = fiber.return;
507
+ nextEffect = sibling;
508
+ return;
509
+ }
510
+
511
+ nextEffect = fiber.return;
512
+ }
513
+}
514
+
515
+function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
516
+ const current = finishedWork.alternate;
517
+ const flags = finishedWork.flags;
518
+
519
+ if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
520
+ // Check to see if the focused element was inside of a hidden (Suspense) subtree.
521
+ if (
522
+ // TODO: Can optimize this further with separate Hide and Show flags. We
523
+ // only care about Hide here.
524
+ (flags & Visibility) !== NoFlags &&
525
+ finishedWork.tag === SuspenseComponent &&
526
+ isSuspenseBoundaryBeingHidden(current, finishedWork) &&
527
+ doesFiberContain(finishedWork, focusedInstanceHandle)
528
+ ) {
529
+ shouldFireAfterActiveInstanceBlur = true;
530
+ beforeActiveInstanceBlur(finishedWork);
531
+ }
532
+ }
533
+
534
+ if ((flags & Snapshot) !== NoFlags) {
535
+ setCurrentDebugFiberInDEV(finishedWork);
536
+ switch (finishedWork.tag) {
537
+ case FunctionComponent:
538
+ case ForwardRef:
539
+ case SimpleMemoComponent:
540
+ case Block: {
541
+ break;
542
+ }
543
+ case ClassComponent: {
544
+ if (finishedWork.flags & Snapshot) {
545
+ if (current !== null) {
546
+ const prevProps = current.memoizedProps;
547
+ const prevState = current.memoizedState;
548
+ const instance = finishedWork.stateNode;
549
+ // We could update instance props and state here,
550
+ // but instead we rely on them being set during last render.
551
+ // TODO: revisit this when we implement resuming.
552
+ if (__DEV__) {
553
+ if (
554
+ finishedWork.type === finishedWork.elementType &&
555
+ !didWarnAboutReassigningProps
556
+ ) {
557
+ if (instance.props !== finishedWork.memoizedProps) {
558
+ console.error(
559
+ 'Expected %s props to match memoized props before ' +
560
+ 'getSnapshotBeforeUpdate. ' +
561
+ 'This might either be because of a bug in React, or because ' +
562
+ 'a component reassigns its own `this.props`. ' +
563
+ 'Please file an issue.',
564
+ getComponentName(finishedWork.type) || 'instance',
565
+ );
566
+ }
567
+ if (instance.state !== finishedWork.memoizedState) {
568
+ console.error(
569
+ 'Expected %s state to match memoized state before ' +
570
+ 'getSnapshotBeforeUpdate. ' +
571
+ 'This might either be because of a bug in React, or because ' +
572
+ 'a component reassigns its own `this.state`. ' +
573
+ 'Please file an issue.',
574
+ getComponentName(finishedWork.type) || 'instance',
575
+ );
576
+ }
577
+ }
578
+ }
579
+ const snapshot = instance.getSnapshotBeforeUpdate(
580
+ finishedWork.elementType === finishedWork.type
581
+ ? prevProps
582
+ : resolveDefaultProps(finishedWork.type, prevProps),
583
+ prevState,
584
+ );
585
+ if (__DEV__) {
586
+ const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
587
+ if (
588
+ snapshot === undefined &&
589
+ !didWarnSet.has(finishedWork.type)
590
+ ) {
591
+ didWarnSet.add(finishedWork.type);
592
+ console.error(
593
+ '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
594
+ 'must be returned. You have returned undefined.',
595
+ getComponentName(finishedWork.type),
596
+ );
597
+ }
598
+ }
599
+ instance.__reactInternalSnapshotBeforeUpdate = snapshot;
600
+ }
601
+ }
602
+ break;
603
+ }
604
+ case HostRoot: {
605
+ if (supportsMutation) {
606
+ if (finishedWork.flags & Snapshot) {
607
+ const root = finishedWork.stateNode;
608
+ clearContainer(root.containerInfo);
609
+ }
610
+ }
611
+ break;
612
+ }
613
+ case HostComponent:
614
+ case HostText:
615
+ case HostPortal:
616
+ case IncompleteClassComponent:
617
+ // Nothing to do for these component types
618
+ break;
619
+ default:
620
+ invariant(
621
+ false,
622
+ 'This unit of work tag should not have side-effects. This error is ' +
623
+ 'likely caused by a bug in React. Please file an issue.',
624
+ );
625
+ }
626
+ resetCurrentDebugFiberInDEV();
627
+ }
628
+}
629
+
630
+function commitBeforeMutationEffectsDeletions(deletions: Array<Fiber>) {
631
+ for (let i = 0; i < deletions.length; i++) {
632
+ const fiber = deletions[i];
633
+
634
+ // TODO (effects) It would be nice to avoid calling doesFiberContain()
635
+ // Maybe we can repurpose one of the subtreeFlags positions for this instead?
636
+ // Use it to store which part of the tree the focused instance is in?
637
+ // This assumes we can safely determine that instance during the "render" phase.
638
+
639
+ if (doesFiberContain(fiber, ((focusedInstanceHandle: any): Fiber))) {
640
+ shouldFireAfterActiveInstanceBlur = true;
641
+ beforeActiveInstanceBlur(fiber);
642
+ }
643
+ }
644
+}
645
+
646
+export function commitMutationEffects(
647
+ firstChild: Fiber,
648
+ root: FiberRoot,
649
+ renderPriorityLevel: ReactPriorityLevel,
650
+) {
651
+ if (enableRecursiveCommitTraversal) {
652
+ recursivelyCommitMutationEffects(firstChild, root, renderPriorityLevel);
653
+ } else {
654
+ nextEffect = firstChild;
655
+ iterativelyCommitMutationEffects_begin(root, renderPriorityLevel);
656
+ }
657
+}
658
+
659
+function recursivelyCommitMutationEffects(
660
+ firstChild: Fiber,
661
+ root: FiberRoot,
662
+ renderPriorityLevel: ReactPriorityLevel,
663
+) {
664
+ let fiber = firstChild;
665
+ while (fiber !== null) {
666
+ const deletions = fiber.deletions;
667
+ if (deletions !== null) {
668
+ commitMutationEffectsDeletions(
669
+ deletions,
670
+ fiber,
671
+ root,
672
+ renderPriorityLevel,
673
+ );
674
+ }
675
+
676
+ if (fiber.child !== null) {
677
+ const mutationFlags = fiber.subtreeFlags & MutationMask;
678
+ if (mutationFlags !== NoFlags) {
679
+ recursivelyCommitMutationEffects(
680
+ fiber.child,
681
+ root,
682
+ renderPriorityLevel,
683
+ );
684
+ }
685
+ }
686
+
687
+ if (__DEV__) {
688
+ setCurrentDebugFiberInDEV(fiber);
689
+ invokeGuardedCallback(
690
+ null,
691
+ commitMutationEffectsOnFiber,
692
+ null,
693
+ fiber,
694
+ root,
695
+ renderPriorityLevel,
696
+ );
697
+ if (hasCaughtError()) {
698
+ const error = clearCaughtError();
699
+ captureCommitPhaseError(fiber, fiber.return, error);
700
+ }
701
+ resetCurrentDebugFiberInDEV();
702
+ } else {
703
+ try {
704
+ commitMutationEffectsOnFiber(fiber, root, renderPriorityLevel);
705
+ } catch (error) {
706
+ captureCommitPhaseError(fiber, fiber.return, error);
707
+ }
708
+ }
709
+ fiber = fiber.sibling;
710
+ }
711
+}
712
+
713
+function iterativelyCommitMutationEffects_begin(
714
+ root: FiberRoot,
715
+ renderPriorityLevel: ReactPriorityLevel,
716
+) {
717
+ while (nextEffect !== null) {
718
+ const fiber = nextEffect;
719
+
720
+ // TODO: Should wrap this in flags check, too, as optimization
721
+ const deletions = fiber.deletions;
722
+ if (deletions !== null) {
723
+ commitMutationEffectsDeletions(
724
+ deletions,
725
+ fiber,
726
+ root,
727
+ renderPriorityLevel,
728
+ );
729
+ }
730
+
731
+ const child = fiber.child;
732
+ if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
733
+ child.return = fiber;
734
+ nextEffect = child;
735
+ } else {
736
+ iterativelyCommitMutationEffects_complete(root, renderPriorityLevel);
737
+ }
738
+ }
739
+}
740
+
741
+function iterativelyCommitMutationEffects_complete(
742
+ root: FiberRoot,
743
+ renderPriorityLevel: ReactPriorityLevel,
744
+) {
745
+ while (nextEffect !== null) {
746
+ const fiber = nextEffect;
747
+ if (__DEV__) {
748
+ setCurrentDebugFiberInDEV(fiber);
749
+ invokeGuardedCallback(
750
+ null,
751
+ commitMutationEffectsOnFiber,
752
+ null,
753
+ fiber,
754
+ root,
755
+ renderPriorityLevel,
756
+ );
757
+ if (hasCaughtError()) {
758
+ const error = clearCaughtError();
759
+ captureCommitPhaseError(fiber, fiber.return, error);
760
+ }
761
+ resetCurrentDebugFiberInDEV();
762
+ } else {
763
+ try {
764
+ commitMutationEffectsOnFiber(fiber, root, renderPriorityLevel);
765
+ } catch (error) {
766
+ captureCommitPhaseError(fiber, fiber.return, error);
767
+ }
768
+ }
769
+
770
+ const sibling = fiber.sibling;
771
+ if (sibling !== null) {
772
+ sibling.return = fiber.return;
773
+ nextEffect = sibling;
774
+ return;
775
+ }
776
+
777
+ nextEffect = fiber.return;
778
+ }
779
+}
780
+
781
+function commitMutationEffectsOnFiber(
782
+ fiber: Fiber,
783
+ root: FiberRoot,
784
+ renderPriorityLevel,
785
+) {
786
+ const flags = fiber.flags;
787
+ if (flags & ContentReset) {
788
+ commitResetTextContent(fiber);
789
+ }
790
+
791
+ if (flags & Ref) {
792
+ const current = fiber.alternate;
793
+ if (current !== null) {
794
+ commitDetachRef(current);
795
+ }
796
+ if (enableScopeAPI) {
797
+ // TODO: This is a temporary solution that allowed us to transition away from React Flare on www.
798
+ if (fiber.tag === ScopeComponent) {
799
+ commitAttachRef(fiber);
800
+ }
801
+ }
802
+ }
803
+
804
+ // The following switch statement is only concerned about placement,
805
+ // updates, and deletions. To avoid needing to add a case for every possible
806
+ // bitmap value, we remove the secondary effects from the effect tag and
807
+ // switch on that value.
808
+ const primaryFlags = flags & (Placement | Update | Hydrating);
809
+ switch (primaryFlags) {
810
+ case Placement: {
811
+ commitPlacement(fiber);
812
+ // Clear the "placement" from effect tag so that we know that this is
813
+ // inserted, before any life-cycles like componentDidMount gets called.
814
+ // TODO: findDOMNode doesn't rely on this any more but isMounted does
815
+ // and isMounted is deprecated anyway so we should be able to kill this.
816
+ fiber.flags &= ~Placement;
817
+ break;
818
+ }
819
+ case PlacementAndUpdate: {
820
+ // Placement
821
+ commitPlacement(fiber);
822
+ // Clear the "placement" from effect tag so that we know that this is
823
+ // inserted, before any life-cycles like componentDidMount gets called.
824
+ fiber.flags &= ~Placement;
825
+
826
+ // Update
827
+ const current = fiber.alternate;
828
+ commitWork(current, fiber);
829
+ break;
830
+ }
831
+ case Hydrating: {
832
+ fiber.flags &= ~Hydrating;
833
+ break;
834
+ }
835
+ case HydratingAndUpdate: {
836
+ fiber.flags &= ~Hydrating;
837
+
838
+ // Update
839
+ const current = fiber.alternate;
840
+ commitWork(current, fiber);
841
+ break;
842
+ }
843
+ case Update: {
844
+ const current = fiber.alternate;
845
+ commitWork(current, fiber);
846
+ break;
847
+ }
848
+ }
849
+}
850
+
851
+function commitMutationEffectsDeletions(
852
+ deletions: Array<Fiber>,
853
+ nearestMountedAncestor: Fiber,
854
+ root: FiberRoot,
855
+ renderPriorityLevel,
856
+) {
857
+ for (let i = 0; i < deletions.length; i++) {
858
+ const childToDelete = deletions[i];
859
+ if (__DEV__) {
860
+ invokeGuardedCallback(
861
+ null,
862
+ commitDeletion,
863
+ null,
864
+ root,
865
+ childToDelete,
866
+ nearestMountedAncestor,
867
+ renderPriorityLevel,
868
+ );
869
+ if (hasCaughtError()) {
870
+ const error = clearCaughtError();
871
+ captureCommitPhaseError(childToDelete, nearestMountedAncestor, error);
872
+ }
873
+ } else {
874
+ try {
875
+ commitDeletion(
876
+ root,
877
+ childToDelete,
878
+ nearestMountedAncestor,
879
+ renderPriorityLevel,
880
+ );
881
+ } catch (error) {
882
+ captureCommitPhaseError(childToDelete, nearestMountedAncestor, error);
883
+ }
884
+ }
885
+ }
886
+}
887
+
888
+export function commitLayoutEffects(
889
+ finishedWork: Fiber,
890
+ finishedRoot: FiberRoot,
891
+) {
892
+ if (enableRecursiveCommitTraversal) {
893
+ if (__DEV__) {
894
+ setCurrentDebugFiberInDEV(finishedWork);
895
+ invokeGuardedCallback(
896
+ null,
897
+ recursivelyCommitLayoutEffects,
898
+ null,
899
+ finishedWork,
900
+ finishedRoot,
901
+ );
902
+ if (hasCaughtError()) {
903
+ const error = clearCaughtError();
904
+ captureCommitPhaseError(finishedWork, null, error);
905
+ }
906
+ resetCurrentDebugFiberInDEV();
907
+ } else {
908
+ try {
909
+ recursivelyCommitLayoutEffects(finishedWork, finishedRoot);
910
+ } catch (error) {
911
+ captureCommitPhaseError(finishedWork, null, error);
912
+ }
913
+ }
914
+ } else {
915
+ nextEffect = finishedWork;
916
+ iterativelyCommitLayoutEffects_begin(finishedWork, finishedRoot);
917
+ }
918
+}
919
+
920
function recursivelyCommitLayoutEffects(
921
finishedWork: Fiber,
922
finishedRoot: FiberRoot,
@@ -660,18 +1115,229 @@ function recursivelyCommitLayoutEffects(
1115
}
1116
}
1117
}
663
-
664
- if (enableScopeAPI) {
665
- // TODO: This is a temporary solution that allowed us to transition away from React Flare on www.
666
- if (flags & Ref && tag !== ScopeComponent) {
667
- commitAttachRef(finishedWork);
668
- }
669
- } else {
670
- if (flags & Ref) {
671
- commitAttachRef(finishedWork);
672
- }
1118
+
1119
+ if (enableScopeAPI) {
1120
+ // TODO: This is a temporary solution that allowed us to transition away from React Flare on www.
1121
+ if (flags & Ref && tag !== ScopeComponent) {
1122
+ commitAttachRef(finishedWork);
1123
+ }
1124
+ } else {
1125
+ if (flags & Ref) {
1126
+ commitAttachRef(finishedWork);
1127
+ }
1128
+ }
1129
+ break;
1130
+ }
1131
+ }
1132
+}
1133
+
1134
+function iterativelyCommitLayoutEffects_begin(
1135
+ subtreeRoot: Fiber,
1136
+ finishedRoot: FiberRoot,
1137
+) {
1138
+ while (nextEffect !== null) {
1139
+ const finishedWork: Fiber = nextEffect;
1140
+ const firstChild = finishedWork.child;
1141
+
1142
+ if (
1143
+ (finishedWork.subtreeFlags & LayoutMask) !== NoFlags &&
1144
+ firstChild !== null
1145
+ ) {
1146
+ if (
1147
+ enableProfilerTimer &&
1148
+ enableProfilerCommitHooks &&
1149
+ finishedWork.tag === Profiler
1150
+ ) {
1151
+ const prevProfilerOnStack = nearestProfilerOnStack;
1152
+ nearestProfilerOnStack = finishedWork;
1153
+
1154
+ let child = firstChild;
1155
+ while (child !== null) {
1156
+ nextEffect = child;
1157
+ iterativelyCommitLayoutEffects_begin(child, finishedRoot);
1158
+ child = child.sibling;
1159
+ }
1160
+ nextEffect = finishedWork;
1161
+
1162
+ if ((finishedWork.flags & LayoutMask) !== NoFlags) {
1163
+ if (__DEV__) {
1164
+ setCurrentDebugFiberInDEV(finishedWork);
1165
+ invokeGuardedCallback(
1166
+ null,
1167
+ commitLayoutEffectsForProfiler,
1168
+ null,
1169
+ finishedWork,
1170
+ finishedRoot,
1171
+ );
1172
+ if (hasCaughtError()) {
1173
+ const error = clearCaughtError();
1174
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
1175
+ }
1176
+ resetCurrentDebugFiberInDEV();
1177
+ } else {
1178
+ try {
1179
+ commitLayoutEffectsForProfiler(finishedWork, finishedRoot);
1180
+ } catch (error) {
1181
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
1182
+ }
1183
+ }
1184
+ }
1185
+
1186
+ // Propagate layout effect durations to the next nearest Profiler ancestor.
1187
+ // Do not reset these values until the next render so DevTools has a chance to read them first.
1188
+ if (prevProfilerOnStack !== null) {
1189
+ prevProfilerOnStack.stateNode.effectDuration +=
1190
+ finishedWork.stateNode.effectDuration;
1191
+ }
1192
+ nearestProfilerOnStack = prevProfilerOnStack;
1193
+
1194
+ if (finishedWork === subtreeRoot) {
1195
+ nextEffect = null;
1196
+ return;
1197
+ }
1198
+ const sibling = finishedWork.sibling;
1199
+ if (sibling !== null) {
1200
+ sibling.return = finishedWork.return;
1201
+ nextEffect = sibling;
1202
+ } else {
1203
+ nextEffect = finishedWork.return;
1204
+ iterativelyCommitLayoutEffects_complete(subtreeRoot, finishedRoot);
1205
+ }
1206
+ } else {
1207
+ firstChild.return = finishedWork;
1208
+ nextEffect = firstChild;
1209
+ }
1210
+ } else {
1211
+ iterativelyCommitLayoutEffects_complete(subtreeRoot, finishedRoot);
1212
+ }
1213
+ }
1214
+}
1215
+
1216
+function iterativelyCommitLayoutEffects_complete(
1217
+ subtreeRoot: Fiber,
1218
+ finishedRoot: FiberRoot,
1219
+) {
1220
+ while (nextEffect !== null) {
1221
+ const fiber = nextEffect;
1222
+
1223
+ if ((fiber.flags & LayoutMask) !== NoFlags) {
1224
+ if (__DEV__) {
1225
+ setCurrentDebugFiberInDEV(fiber);
1226
+ invokeGuardedCallback(
1227
+ null,
1228
+ commitLayoutEffectsOnFiber,
1229
+ null,
1230
+ finishedRoot,
1231
+ fiber,
1232
+ );
1233
+ if (hasCaughtError()) {
1234
+ const error = clearCaughtError();
1235
+ captureCommitPhaseError(fiber, fiber.return, error);
1236
+ }
1237
+ resetCurrentDebugFiberInDEV();
1238
+ } else {
1239
+ try {
1240
+ commitLayoutEffectsOnFiber(finishedRoot, fiber);
1241
+ } catch (error) {
1242
+ captureCommitPhaseError(fiber, fiber.return, error);
1243
+ }
1244
+ }
1245
+ }
1246
+
1247
+ if (fiber === subtreeRoot) {
1248
+ nextEffect = null;
1249
+ return;
1250
+ }
1251
+
1252
+ const sibling = fiber.sibling;
1253
+ if (sibling !== null) {
1254
+ sibling.return = fiber.return;
1255
+ nextEffect = sibling;
1256
+ return;
1257
+ }
1258
+
1259
+ nextEffect = nextEffect.return;
1260
+ }
1261
+}
1262
+
1263
+function commitLayoutEffectsOnFiber(
1264
+ finishedRoot: FiberRoot,
1265
+ finishedWork: Fiber,
1266
+) {
1267
+ const tag = finishedWork.tag;
1268
+ const flags = finishedWork.flags;
1269
+ if ((flags & (Update | Callback)) !== NoFlags) {
1270
+ switch (tag) {
1271
+ case FunctionComponent:
1272
+ case ForwardRef:
1273
+ case SimpleMemoComponent:
1274
+ case Block: {
1275
+ if (
1276
+ enableProfilerTimer &&
1277
+ enableProfilerCommitHooks &&
1278
+ finishedWork.mode & ProfileMode
1279
+ ) {
1280
+ try {
1281
+ startLayoutEffectTimer();
1282
+ commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
1283
+ } finally {
1284
+ recordLayoutEffectDuration(finishedWork);
1285
+ }
1286
+ } else {
1287
+ commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
1288
+ }
1289
+ break;
1290
+ }
1291
+ case ClassComponent: {
1292
+ // NOTE: Layout effect durations are measured within this function.
1293
+ commitLayoutEffectsForClassComponent(finishedWork);
1294
+ break;
1295
}
674
- break;
1296
+ case HostRoot: {
1297
+ commitLayoutEffectsForHostRoot(finishedWork);
1298
+ break;
1299
+ }
1300
+ case HostComponent: {
1301
+ commitLayoutEffectsForHostComponent(finishedWork);
1302
+ break;
1303
+ }
1304
+ case Profiler: {
1305
+ commitLayoutEffectsForProfiler(finishedWork, finishedRoot);
1306
+ break;
1307
+ }
1308
+ case SuspenseComponent: {
1309
+ commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1310
+ break;
1311
+ }
1312
+ case FundamentalComponent:
1313
+ case HostPortal:
1314
+ case HostText:
1315
+ case IncompleteClassComponent:
1316
+ case LegacyHiddenComponent:
1317
+ case OffscreenComponent:
1318
+ case ScopeComponent:
1319
+ case SuspenseListComponent: {
1320
+ // We have no life-cycles associated with these component types.
1321
+ break;
1322
+ }
1323
+ default: {
1324
+ invariant(
1325
+ false,
1326
+ 'This unit of work tag should not have side-effects. This error is ' +
1327
+ 'likely caused by a bug in React. Please file an issue.',
1328
+ );
1329
+ }
1330
+ }
1331
+ }
1332
+
1333
+ if (enableScopeAPI) {
1334
+ // TODO: This is a temporary solution that allowed us to transition away from React Flare on www.
1335
+ if (flags & Ref && tag !== ScopeComponent) {
1336
+ commitAttachRef(finishedWork);
1337
+ }
1338
+ } else {
1339
+ if (flags & Ref) {
1340
+ commitAttachRef(finishedWork);
1341
}
1342
}
1343
}
@@ -972,6 +1638,401 @@ function hideOrUnhideAllChildren(finishedWork, isHidden) {
1638
}
1639
}
1640
1641
+export function commitPassiveMountEffects(
1642
+ root: FiberRoot,
1643
+ firstChild: Fiber,
1644
+): void {
1645
+ if (enableRecursiveCommitTraversal) {
1646
+ recursivelyCommitPassiveMountEffects(root, firstChild);
1647
+ } else {
1648
+ nextEffect = firstChild;
1649
+ iterativelyCommitPassiveMountEffects_begin(firstChild, root);
1650
+ }
1651
+}
1652
+
1653
+function recursivelyCommitPassiveMountEffects(
1654
+ root: FiberRoot,
1655
+ firstChild: Fiber,
1656
+): void {
1657
+ let fiber = firstChild;
1658
+ while (fiber !== null) {
1659
+ let prevProfilerOnStack = null;
1660
+ if (enableProfilerTimer && enableProfilerCommitHooks) {
1661
+ if (fiber.tag === Profiler) {
1662
+ prevProfilerOnStack = nearestProfilerOnStack;
1663
+ nearestProfilerOnStack = fiber;
1664
+ }
1665
+ }
1666
+
1667
+ const primarySubtreeFlags = fiber.subtreeFlags & PassiveMask;
1668
+
1669
+ if (fiber.child !== null && primarySubtreeFlags !== NoFlags) {
1670
+ recursivelyCommitPassiveMountEffects(root, fiber.child);
1671
+ }
1672
+
1673
+ if ((fiber.flags & Passive) !== NoFlags) {
1674
+ if (__DEV__) {
1675
+ setCurrentDebugFiberInDEV(fiber);
1676
+ invokeGuardedCallback(
1677
+ null,
1678
+ commitPassiveMountOnFiber,
1679
+ null,
1680
+ root,
1681
+ fiber,
1682
+ );
1683
+ if (hasCaughtError()) {
1684
+ const error = clearCaughtError();
1685
+ captureCommitPhaseError(fiber, fiber.return, error);
1686
+ }
1687
+ resetCurrentDebugFiberInDEV();
1688
+ } else {
1689
+ try {
1690
+ commitPassiveMountOnFiber(root, fiber);
1691
+ } catch (error) {
1692
+ captureCommitPhaseError(fiber, fiber.return, error);
1693
+ }
1694
+ }
1695
+ }
1696
+
1697
+ if (enableProfilerTimer && enableProfilerCommitHooks) {
1698
+ if (fiber.tag === Profiler) {
1699
+ // Bubble times to the next nearest ancestor Profiler.
1700
+ // After we process that Profiler, we'll bubble further up.
1701
+ if (prevProfilerOnStack !== null) {
1702
+ prevProfilerOnStack.stateNode.passiveEffectDuration +=
1703
+ fiber.stateNode.passiveEffectDuration;
1704
+ }
1705
+
1706
+ nearestProfilerOnStack = prevProfilerOnStack;
1707
+ }
1708
+ }
1709
+
1710
+ fiber = fiber.sibling;
1711
+ }
1712
+}
1713
+
1714
+function iterativelyCommitPassiveMountEffects_begin(
1715
+ subtreeRoot: Fiber,
1716
+ root: FiberRoot,
1717
+) {
1718
+ while (nextEffect !== null) {
1719
+ const fiber = nextEffect;
1720
+ const firstChild = fiber.child;
1721
+ if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && firstChild !== null) {
1722
+ if (
1723
+ enableProfilerTimer &&
1724
+ enableProfilerCommitHooks &&
1725
+ fiber.tag === Profiler
1726
+ ) {
1727
+ const prevProfilerOnStack = nearestProfilerOnStack;
1728
+ nearestProfilerOnStack = fiber;
1729
+
1730
+ let child = firstChild;
1731
+ while (child !== null) {
1732
+ nextEffect = child;
1733
+ iterativelyCommitPassiveMountEffects_begin(child, root);
1734
+ child = child.sibling;
1735
+ }
1736
+ nextEffect = fiber;
1737
+
1738
+ if ((fiber.flags & PassiveMask) !== NoFlags) {
1739
+ if (__DEV__) {
1740
+ setCurrentDebugFiberInDEV(fiber);
1741
+ invokeGuardedCallback(
1742
+ null,
1743
+ commitProfilerPassiveEffect,
1744
+ null,
1745
+ root,
1746
+ fiber,
1747
+ );
1748
+ if (hasCaughtError()) {
1749
+ const error = clearCaughtError();
1750
+ captureCommitPhaseError(fiber, fiber.return, error);
1751
+ }
1752
+ resetCurrentDebugFiberInDEV();
1753
+ } else {
1754
+ try {
1755
+ commitProfilerPassiveEffect(root, fiber);
1756
+ } catch (error) {
1757
+ captureCommitPhaseError(fiber, fiber.return, error);
1758
+ }
1759
+ }
1760
+ }
1761
+
1762
+ // Bubble times to the next nearest ancestor Profiler.
1763
+ // After we process that Profiler, we'll bubble further up.
1764
+ if (prevProfilerOnStack !== null) {
1765
+ prevProfilerOnStack.stateNode.passiveEffectDuration +=
1766
+ fiber.stateNode.passiveEffectDuration;
1767
+ }
1768
+
1769
+ nearestProfilerOnStack = prevProfilerOnStack;
1770
+
1771
+ if (fiber === subtreeRoot) {
1772
+ nextEffect = null;
1773
+ return;
1774
+ }
1775
+ const sibling = fiber.sibling;
1776
+ if (sibling !== null) {
1777
+ sibling.return = fiber.return;
1778
+ nextEffect = sibling;
1779
+ } else {
1780
+ nextEffect = fiber.return;
1781
+ iterativelyCommitPassiveMountEffects_complete(subtreeRoot, root);
1782
+ }
1783
+ } else {
1784
+ firstChild.return = fiber;
1785
+ nextEffect = firstChild;
1786
+ }
1787
+ } else {
1788
+ iterativelyCommitPassiveMountEffects_complete(subtreeRoot, root);
1789
+ }
1790
+ }
1791
+}
1792
+
1793
+function iterativelyCommitPassiveMountEffects_complete(
1794
+ subtreeRoot: Fiber,
1795
+ root: FiberRoot,
1796
+) {
1797
+ while (nextEffect !== null) {
1798
+ const fiber = nextEffect;
1799
+ if ((fiber.flags & Passive) !== NoFlags) {
1800
+ if (__DEV__) {
1801
+ setCurrentDebugFiberInDEV(fiber);
1802
+ invokeGuardedCallback(
1803
+ null,
1804
+ commitPassiveMountOnFiber,
1805
+ null,
1806
+ root,
1807
+ fiber,
1808
+ );
1809
+ if (hasCaughtError()) {
1810
+ const error = clearCaughtError();
1811
+ captureCommitPhaseError(fiber, fiber.return, error);
1812
+ }
1813
+ resetCurrentDebugFiberInDEV();
1814
+ } else {
1815
+ try {
1816
+ commitPassiveMountOnFiber(root, fiber);
1817
+ } catch (error) {
1818
+ captureCommitPhaseError(fiber, fiber.return, error);
1819
+ }
1820
+ }
1821
+ }
1822
+
1823
+ if (fiber === subtreeRoot) {
1824
+ nextEffect = null;
1825
+ return;
1826
+ }
1827
+
1828
+ const sibling = fiber.sibling;
1829
+ if (sibling !== null) {
1830
+ sibling.return = fiber.return;
1831
+ nextEffect = sibling;
1832
+ return;
1833
+ }
1834
+
1835
+ nextEffect = fiber.return;
1836
+ }
1837
+}
1838
+
1839
+export function commitPassiveUnmountEffects(firstChild: Fiber): void {
1840
+ if (enableRecursiveCommitTraversal) {
1841
+ recursivelyCommitPassiveUnmountEffects(firstChild);
1842
+ } else {
1843
+ nextEffect = firstChild;
1844
+ iterativelyCommitPassiveUnmountEffects_begin();
1845
+ }
1846
+}
1847
+
1848
+function recursivelyCommitPassiveUnmountEffects(firstChild: Fiber): void {
1849
+ let fiber = firstChild;
1850
+ while (fiber !== null) {
1851
+ const deletions = fiber.deletions;
1852
+ if (deletions !== null) {
1853
+ for (let i = 0; i < deletions.length; i++) {
1854
+ const fiberToDelete = deletions[i];
1855
+ recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree(
1856
+ fiberToDelete,
1857
+ fiber,
1858
+ );
1859
+
1860
+ // Now that passive effects have been processed, it's safe to detach lingering pointers.
1861
+ detachFiberAfterEffects(fiberToDelete);
1862
+ }
1863
+ }
1864
+
1865
+ const child = fiber.child;
1866
+ if (child !== null) {
1867
+ // If any children have passive effects then traverse the subtree.
1868
+ // Note that this requires checking subtreeFlags of the current Fiber,
1869
+ // rather than the subtreeFlags/effectsTag of the first child,
1870
+ // since that would not cover passive effects in siblings.
1871
+ const passiveFlags = fiber.subtreeFlags & PassiveMask;
1872
+ if (passiveFlags !== NoFlags) {
1873
+ recursivelyCommitPassiveUnmountEffects(child);
1874
+ }
1875
+ }
1876
+
1877
+ const primaryFlags = fiber.flags & Passive;
1878
+ if (primaryFlags !== NoFlags) {
1879
+ setCurrentDebugFiberInDEV(fiber);
1880
+ commitPassiveUnmountOnFiber(fiber);
1881
+ resetCurrentDebugFiberInDEV();
1882
+ }
1883
+
1884
+ fiber = fiber.sibling;
1885
+ }
1886
+}
1887
+
1888
+function iterativelyCommitPassiveUnmountEffects_begin() {
1889
+ while (nextEffect !== null) {
1890
+ const fiber = nextEffect;
1891
+ const child = fiber.child;
1892
+
1893
+ // TODO: Should wrap this in flags check, too, as optimization
1894
+ const deletions = fiber.deletions;
1895
+ if (deletions !== null) {
1896
+ for (let i = 0; i < deletions.length; i++) {
1897
+ const fiberToDelete = deletions[i];
1898
+ nextEffect = fiberToDelete;
1899
+ iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_begin(
1900
+ fiberToDelete,
1901
+ fiber,
1902
+ );
1903
+
1904
+ // Now that passive effects have been processed, it's safe to detach lingering pointers.
1905
+ detachFiberAfterEffects(fiberToDelete);
1906
+ }
1907
+ nextEffect = fiber;
1908
+ }
1909
+
1910
+ if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && child !== null) {
1911
+ child.return = fiber;
1912
+ nextEffect = child;
1913
+ } else {
1914
+ iterativelyCommitPassiveUnmountEffects_complete();
1915
+ }
1916
+ }
1917
+}
1918
+
1919
+function iterativelyCommitPassiveUnmountEffects_complete() {
1920
+ while (nextEffect !== null) {
1921
+ const fiber = nextEffect;
1922
+ if ((fiber.flags & Passive) !== NoFlags) {
1923
+ setCurrentDebugFiberInDEV(fiber);
1924
+ commitPassiveUnmountOnFiber(fiber);
1925
+ resetCurrentDebugFiberInDEV();
1926
+ }
1927
+
1928
+ const sibling = fiber.sibling;
1929
+ if (sibling !== null) {
1930
+ sibling.return = fiber.return;
1931
+ nextEffect = sibling;
1932
+ return;
1933
+ }
1934
+
1935
+ nextEffect = fiber.return;
1936
+ }
1937
+}
1938
+
1939
+function recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree(
1940
+ fiberToDelete: Fiber,
1941
+ nearestMountedAncestor: Fiber,
1942
+): void {
1943
+ if ((fiberToDelete.subtreeFlags & PassiveStatic) !== NoFlags) {
1944
+ // If any children have passive effects then traverse the subtree.
1945
+ // Note that this requires checking subtreeFlags of the current Fiber,
1946
+ // rather than the subtreeFlags/effectsTag of the first child,
1947
+ // since that would not cover passive effects in siblings.
1948
+ let child = fiberToDelete.child;
1949
+ while (child !== null) {
1950
+ recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree(
1951
+ child,
1952
+ nearestMountedAncestor,
1953
+ );
1954
+ child = child.sibling;
1955
+ }
1956
+ }
1957
+
1958
+ if ((fiberToDelete.flags & PassiveStatic) !== NoFlags) {
1959
+ setCurrentDebugFiberInDEV(fiberToDelete);
1960
+ commitPassiveUnmountInsideDeletedTreeOnFiber(
1961
+ fiberToDelete,
1962
+ nearestMountedAncestor,
1963
+ );
1964
+ resetCurrentDebugFiberInDEV();
1965
+ }
1966
+}
1967
+
1968
+function iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_begin(
1969
+ deletedSubtreeRoot: Fiber,
1970
+ nearestMountedAncestor: Fiber,
1971
+) {
1972
+ while (nextEffect !== null) {
1973
+ const fiber = nextEffect;
1974
+ const child = fiber.child;
1975
+ if ((fiber.subtreeFlags & PassiveStatic) !== NoFlags && child !== null) {
1976
+ child.return = fiber;
1977
+ nextEffect = child;
1978
+ } else {
1979
+ iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_complete(
1980
+ deletedSubtreeRoot,
1981
+ nearestMountedAncestor,
1982
+ );
1983
+ }
1984
+ }
1985
+}
1986
+
1987
+function iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_complete(
1988
+ deletedSubtreeRoot: Fiber,
1989
+ nearestMountedAncestor: Fiber,
1990
+) {
1991
+ while (nextEffect !== null) {
1992
+ const fiber = nextEffect;
1993
+ if ((fiber.flags & PassiveStatic) !== NoFlags) {
1994
+ setCurrentDebugFiberInDEV(fiber);
1995
+ commitPassiveUnmountInsideDeletedTreeOnFiber(
1996
+ fiber,
1997
+ nearestMountedAncestor,
1998
+ );
1999
+ resetCurrentDebugFiberInDEV();
2000
+ }
2001
+
2002
+ if (fiber === deletedSubtreeRoot) {
2003
+ nextEffect = null;
2004
+ return;
2005
+ }
2006
+
2007
+ const sibling = fiber.sibling;
2008
+ if (sibling !== null) {
2009
+ sibling.return = fiber.return;
2010
+ nextEffect = sibling;
2011
+ return;
2012
+ }
2013
+
2014
+ nextEffect = fiber.return;
2015
+ }
2016
+}
2017
+
2018
+function detachFiberAfterEffects(fiber: Fiber): void {
2019
+ // Null out fields to improve GC for references that may be lingering (e.g. DevTools).
2020
+ // Note that we already cleared the return pointer in detachFiberMutation().
2021
+ fiber.child = null;
2022
+ fiber.deletions = null;
2023
+ fiber.dependencies = null;
2024
+ fiber.memoizedProps = null;
2025
+ fiber.memoizedState = null;
2026
+ fiber.pendingProps = null;
2027
+ fiber.sibling = null;
2028
+ fiber.stateNode = null;
2029
+ fiber.updateQueue = null;
2030
+
2031
+ if (__DEV__) {
2032
+ fiber._debugOwner = null;
2033
+ }
2034
+}
2035
+
2036
function commitAttachRef(finishedWork: Fiber) {
2037
const ref = finishedWork.ref;
2038
if (ref !== null) {
@@ -1950,7 +3011,7 @@ function attachSuspenseRetryListeners(finishedWork: Fiber) {
3011
// This function detects when a Suspense boundary goes from visible to hidden.
3012
// It returns false if the boundary is already hidden.
3013
// TODO: Use an effect tag.
1953
-export function isSuspenseBoundaryBeingHidden(
3014
+function isSuspenseBoundaryBeingHidden(
3015
current: Fiber | null,
3016
finishedWork: Fiber,
3017
): boolean {
@@ -1971,7 +3032,7 @@ function commitResetTextContent(current: Fiber): void {
3032
resetTextContent(current.stateNode);
3033
}
3034
1974
-function commitPassiveUnmount(finishedWork: Fiber): void {
3035
+function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
3036
switch (finishedWork.tag) {
3037
case FunctionComponent:
3038
case ForwardRef:
@@ -2001,7 +3062,7 @@ function commitPassiveUnmount(finishedWork: Fiber): void {
3062
}
3063
}
3064
2004
-function commitPassiveUnmountInsideDeletedTree(
3065
+function commitPassiveUnmountInsideDeletedTreeOnFiber(
3066
current: Fiber,
3067
nearestMountedAncestor: Fiber | null,
3068
): void {
@@ -2034,7 +3095,7 @@ function commitPassiveUnmountInsideDeletedTree(
3095
}
3096
}
3097
2037
-function commitPassiveMount(
3098
+function commitPassiveMountOnFiber(
3099
finishedRoot: FiberRoot,
3100
finishedWork: Fiber,
3101
): void {
@@ -2188,20 +3249,57 @@ function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
3249
}
3250
}
3251
2191
-export {
2192
- commitBeforeMutationLifeCycles,
2193
- commitResetTextContent,
2194
- commitPlacement,
2195
- commitDeletion,
2196
- commitWork,
2197
- commitAttachRef,
2198
- commitDetachRef,
2199
- commitPassiveUnmount,
2200
- commitPassiveUnmountInsideDeletedTree,
2201
- commitPassiveMount,
2202
- invokeLayoutEffectMountInDEV,
2203
- invokeLayoutEffectUnmountInDEV,
2204
- invokePassiveEffectMountInDEV,
2205
- invokePassiveEffectUnmountInDEV,
2206
- recursivelyCommitLayoutEffects,
2207
-};
3252
+// TODO: Convert this to iteration instead of recursion, too. Leaving this for
3253
+// a follow up because the flag is off.
3254
+export function commitDoubleInvokeEffectsInDEV(
3255
+ fiber: Fiber,
3256
+ hasPassiveEffects: boolean,
3257
+) {
3258
+ if (__DEV__ && enableDoubleInvokingEffects) {
3259
+ // Never double-invoke effects for legacy roots.
3260
+ if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) {
3261
+ return;
3262
+ }
3263
+
3264
+ setCurrentDebugFiberInDEV(fiber);
3265
+ invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
3266
+ if (hasPassiveEffects) {
3267
+ invokeEffectsInDev(
3268
+ fiber,
3269
+ MountPassiveDev,
3270
+ invokePassiveEffectUnmountInDEV,
3271
+ );
3272
+ }
3273
+
3274
+ invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
3275
+ if (hasPassiveEffects) {
3276
+ invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
3277
+ }
3278
+ resetCurrentDebugFiberInDEV();
3279
+ }
3280
+}
3281
+
3282
+function invokeEffectsInDev(
3283
+ firstChild: Fiber,
3284
+ fiberFlags: Flags,
3285
+ invokeEffectFn: (fiber: Fiber) => void,
3286
+): void {
3287
+ if (__DEV__ && enableDoubleInvokingEffects) {
3288
+ // We don't need to re-check for legacy roots here.
3289
+ // This function will not be called within legacy roots.
3290
+ let fiber = firstChild;
3291
+ while (fiber !== null) {
3292
+ if (fiber.child !== null) {
3293
+ const primarySubtreeFlag = fiber.subtreeFlags & fiberFlags;
3294
+ if (primarySubtreeFlag !== NoFlags) {
3295
+ invokeEffectsInDev(fiber.child, fiberFlags, invokeEffectFn);
3296
+ }
3297
+ }
3298
+
3299
+ if ((fiber.flags & fiberFlags) !== NoFlags) {
3300
+ invokeEffectFn(fiber);
3301
+ }
3302
+ fiber = fiber.sibling;
3303
+ }
3304
+ }
3305
+}
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+13
-497
@@ -15,21 +15,18 @@ import type {Interaction} from 'scheduler/src/Tracing';
15
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
16
import type {StackCursor} from './ReactFiberStack.new';
17
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
18
-import type {Flags} from './ReactFiberFlags';
18
19
import {
20
warnAboutDeprecatedLifecycles,
21
enableSuspenseServerRenderer,
22
replayFailedUnitOfWorkWithInvokeGuardedCallback,
23
enableProfilerTimer,
25
- enableProfilerCommitHooks,
24
enableSchedulerTracing,
25
warnAboutUnmockedScheduler,
26
deferRenderPhaseUpdateToNextBatch,
27
decoupleUpdatePriorityFromScheduler,
28
enableDebugTracing,
29
enableSchedulingProfiler,
32
- enableScopeAPI,
30
skipUnmountedBoundaries,
31
enableDoubleInvokingEffects,
32
} from 'shared/ReactFeatureFlags';
@@ -83,13 +80,11 @@ import * as Scheduler from 'scheduler';
80
import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
81
82
import {
86
- prepareForCommit,
83
resetAfterCommit,
84
scheduleTimeout,
85
cancelTimeout,
86
noTimeout,
87
warnsIfNotActing,
92
- beforeActiveInstanceBlur,
88
afterActiveInstanceBlur,
89
clearContainer,
90
} from './ReactFiberHostConfig';
@@ -116,31 +111,19 @@ import {
111
MemoComponent,
112
SimpleMemoComponent,
113
Block,
119
- ScopeComponent,
120
- Profiler,
114
} from './ReactWorkTags';
115
import {LegacyRoot} from './ReactRootTags';
116
import {
117
NoFlags,
118
Placement,
126
- Update,
127
- PlacementAndUpdate,
128
- Ref,
129
- ContentReset,
130
- Snapshot,
131
- Passive,
119
PassiveStatic,
120
Incomplete,
121
HostEffectMask,
122
Hydrating,
136
- HydratingAndUpdate,
137
- Visibility,
123
BeforeMutationMask,
124
MutationMask,
125
LayoutMask,
126
PassiveMask,
142
- MountPassiveDev,
143
- MountLayoutDev,
127
} from './ReactFiberFlags';
128
import {
129
NoLanePriority,
@@ -191,22 +174,12 @@ import {
174
createClassErrorUpdate,
175
} from './ReactFiberThrow.new';
176
import {
194
- commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
195
- commitPlacement,
196
- commitWork,
197
- commitDeletion,
198
- commitPassiveUnmount as commitPassiveUnmountOnFiber,
199
- commitPassiveUnmountInsideDeletedTree as commitPassiveUnmountInsideDeletedTreeOnFiber,
200
- commitPassiveMount as commitPassiveMountOnFiber,
201
- commitDetachRef,
202
- commitAttachRef,
203
- commitResetTextContent,
204
- isSuspenseBoundaryBeingHidden,
205
- invokeLayoutEffectMountInDEV,
206
- invokePassiveEffectMountInDEV,
207
- invokeLayoutEffectUnmountInDEV,
208
- invokePassiveEffectUnmountInDEV,
209
- recursivelyCommitLayoutEffects,
177
+ commitBeforeMutationEffects,
178
+ commitMutationEffects,
179
+ commitLayoutEffects,
180
+ commitPassiveMountEffects,
181
+ commitPassiveUnmountEffects,
182
+ commitDoubleInvokeEffectsInDEV,
183
} from './ReactFiberCommitWork.new';
184
import {enqueueUpdate} from './ReactUpdateQueue.new';
185
import {resetContextDependencies} from './ReactFiberNewContext.new';
@@ -247,7 +220,6 @@ import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
220
221
// Used by `act`
222
import enqueueTask from 'shared/enqueueTask';
250
-import {doesFiberContain} from './ReactFiberTreeReflection';
223
224
const ceil = Math.ceil;
225
@@ -327,9 +299,6 @@ let workInProgressRootRenderTargetTime: number = Infinity;
299
// suspense heuristics and opt out of rendering more content.
300
const RENDER_TIMEOUT_MS = 500;
301
330
-// Used to avoid traversing the return path to find the nearest Profiler ancestor during commit.
331
-let nearestProfilerOnStack: Fiber | null = null;
332
-
302
function resetRenderTimer() {
303
workInProgressRootRenderTargetTime = now() + RENDER_TIMEOUT_MS;
304
}
@@ -374,9 +343,6 @@ let currentEventPendingLanes: Lanes = NoLanes;
343
// We warn about state updates for unmounted components differently in this case.
344
let isFlushingPassiveEffects = false;
345
377
-let focusedInstanceHandle: null | Fiber = null;
378
-let shouldFireAfterActiveInstanceBlur: boolean = false;
379
-
346
export function getWorkInProgressRoot(): FiberRoot | null {
347
return workInProgressRoot;
348
}
@@ -1916,13 +1882,10 @@ function commitRootImpl(root, renderPriorityLevel) {
1882
// The first phase a "before mutation" phase. We use this phase to read the
1883
// state of the host tree right before we mutate it. This is where
1884
// getSnapshotBeforeUpdate is called.
1919
- focusedInstanceHandle = prepareForCommit(root.containerInfo);
1920
- shouldFireAfterActiveInstanceBlur = false;
1921
-
1922
- commitBeforeMutationEffects(finishedWork);
1923
-
1924
- // We no longer need to track the active instance fiber
1925
- focusedInstanceHandle = null;
1885
+ const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
1886
+ root,
1887
+ finishedWork,
1888
+ );
1889
1890
if (enableProfilerTimer) {
1891
// Mark the current commit time to be shared by all Profilers in this
@@ -1957,27 +1920,7 @@ function commitRootImpl(root, renderPriorityLevel) {
1920
markLayoutEffectsStarted(lanes);
1921
}
1922
1960
- if (__DEV__) {
1961
- setCurrentDebugFiberInDEV(finishedWork);
1962
- invokeGuardedCallback(
1963
- null,
1964
- recursivelyCommitLayoutEffects,
1965
- null,
1966
- finishedWork,
1967
- root,
1968
- );
1969
- if (hasCaughtError()) {
1970
- const error = clearCaughtError();
1971
- captureCommitPhaseErrorOnRoot(finishedWork, finishedWork, error);
1972
- }
1973
- resetCurrentDebugFiberInDEV();
1974
- } else {
1975
- try {
1976
- recursivelyCommitLayoutEffects(finishedWork, root);
1977
- } catch (error) {
1978
- captureCommitPhaseErrorOnRoot(finishedWork, finishedWork, error);
1979
- }
1980
- }
1923
+ commitLayoutEffects(finishedWork, root);
1924
1925
if (__DEV__) {
1926
if (enableDebugTracing) {
@@ -2117,238 +2060,6 @@ function commitRootImpl(root, renderPriorityLevel) {
2060
return null;
2061
}
2062
2120
-function commitBeforeMutationEffects(firstChild: Fiber) {
2121
- let fiber = firstChild;
2122
- while (fiber !== null) {
2123
- if (fiber.deletions !== null) {
2124
- commitBeforeMutationEffectsDeletions(fiber.deletions);
2125
- }
2126
-
2127
- if (fiber.child !== null) {
2128
- const primarySubtreeFlags = fiber.subtreeFlags & BeforeMutationMask;
2129
- if (primarySubtreeFlags !== NoFlags) {
2130
- commitBeforeMutationEffects(fiber.child);
2131
- }
2132
- }
2133
-
2134
- if (__DEV__) {
2135
- setCurrentDebugFiberInDEV(fiber);
2136
- invokeGuardedCallback(null, commitBeforeMutationEffectsImpl, null, fiber);
2137
- if (hasCaughtError()) {
2138
- const error = clearCaughtError();
2139
- captureCommitPhaseError(fiber, fiber.return, error);
2140
- }
2141
- resetCurrentDebugFiberInDEV();
2142
- } else {
2143
- try {
2144
- commitBeforeMutationEffectsImpl(fiber);
2145
- } catch (error) {
2146
- captureCommitPhaseError(fiber, fiber.return, error);
2147
- }
2148
- }
2149
- fiber = fiber.sibling;
2150
- }
2151
-}
2152
-
2153
-function commitBeforeMutationEffectsImpl(fiber: Fiber) {
2154
- const current = fiber.alternate;
2155
- const flags = fiber.flags;
2156
-
2157
- if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
2158
- // Check to see if the focused element was inside of a hidden (Suspense) subtree.
2159
- if (
2160
- // TODO: Can optimize this further with separate Hide and Show flags. We
2161
- // only care about Hide here.
2162
- (flags & Visibility) !== NoFlags &&
2163
- fiber.tag === SuspenseComponent &&
2164
- isSuspenseBoundaryBeingHidden(current, fiber) &&
2165
- doesFiberContain(fiber, focusedInstanceHandle)
2166
- ) {
2167
- shouldFireAfterActiveInstanceBlur = true;
2168
- beforeActiveInstanceBlur(fiber);
2169
- }
2170
- }
2171
-
2172
- if ((flags & Snapshot) !== NoFlags) {
2173
- setCurrentDebugFiberInDEV(fiber);
2174
- commitBeforeMutationEffectOnFiber(current, fiber);
2175
- resetCurrentDebugFiberInDEV();
2176
- }
2177
-}
2178
-
2179
-function commitBeforeMutationEffectsDeletions(deletions: Array<Fiber>) {
2180
- for (let i = 0; i < deletions.length; i++) {
2181
- const fiber = deletions[i];
2182
-
2183
- // TODO (effects) It would be nice to avoid calling doesFiberContain()
2184
- // Maybe we can repurpose one of the subtreeFlags positions for this instead?
2185
- // Use it to store which part of the tree the focused instance is in?
2186
- // This assumes we can safely determine that instance during the "render" phase.
2187
-
2188
- if (doesFiberContain(fiber, ((focusedInstanceHandle: any): Fiber))) {
2189
- shouldFireAfterActiveInstanceBlur = true;
2190
- beforeActiveInstanceBlur(fiber);
2191
- }
2192
- }
2193
-}
2194
-
2195
-function commitMutationEffects(
2196
- firstChild: Fiber,
2197
- root: FiberRoot,
2198
- renderPriorityLevel: ReactPriorityLevel,
2199
-) {
2200
- let fiber = firstChild;
2201
- while (fiber !== null) {
2202
- const deletions = fiber.deletions;
2203
- if (deletions !== null) {
2204
- commitMutationEffectsDeletions(
2205
- deletions,
2206
- fiber,
2207
- root,
2208
- renderPriorityLevel,
2209
- );
2210
- }
2211
-
2212
- if (fiber.child !== null) {
2213
- const mutationFlags = fiber.subtreeFlags & MutationMask;
2214
- if (mutationFlags !== NoFlags) {
2215
- commitMutationEffects(fiber.child, root, renderPriorityLevel);
2216
- }
2217
- }
2218
-
2219
- if (__DEV__) {
2220
- setCurrentDebugFiberInDEV(fiber);
2221
- invokeGuardedCallback(
2222
- null,
2223
- commitMutationEffectsImpl,
2224
- null,
2225
- fiber,
2226
- root,
2227
- renderPriorityLevel,
2228
- );
2229
- if (hasCaughtError()) {
2230
- const error = clearCaughtError();
2231
- captureCommitPhaseError(fiber, fiber.return, error);
2232
- }
2233
- resetCurrentDebugFiberInDEV();
2234
- } else {
2235
- try {
2236
- commitMutationEffectsImpl(fiber, root, renderPriorityLevel);
2237
- } catch (error) {
2238
- captureCommitPhaseError(fiber, fiber.return, error);
2239
- }
2240
- }
2241
- fiber = fiber.sibling;
2242
- }
2243
-}
2244
-
2245
-function commitMutationEffectsImpl(
2246
- fiber: Fiber,
2247
- root: FiberRoot,
2248
- renderPriorityLevel,
2249
-) {
2250
- const flags = fiber.flags;
2251
- if (flags & ContentReset) {
2252
- commitResetTextContent(fiber);
2253
- }
2254
-
2255
- if (flags & Ref) {
2256
- const current = fiber.alternate;
2257
- if (current !== null) {
2258
- commitDetachRef(current);
2259
- }
2260
- if (enableScopeAPI) {
2261
- // TODO: This is a temporary solution that allowed us to transition away from React Flare on www.
2262
- if (fiber.tag === ScopeComponent) {
2263
- commitAttachRef(fiber);
2264
- }
2265
- }
2266
- }
2267
-
2268
- // The following switch statement is only concerned about placement,
2269
- // updates, and deletions. To avoid needing to add a case for every possible
2270
- // bitmap value, we remove the secondary effects from the effect tag and
2271
- // switch on that value.
2272
- const primaryFlags = flags & (Placement | Update | Hydrating);
2273
- switch (primaryFlags) {
2274
- case Placement: {
2275
- commitPlacement(fiber);
2276
- // Clear the "placement" from effect tag so that we know that this is
2277
- // inserted, before any life-cycles like componentDidMount gets called.
2278
- // TODO: findDOMNode doesn't rely on this any more but isMounted does
2279
- // and isMounted is deprecated anyway so we should be able to kill this.
2280
- fiber.flags &= ~Placement;
2281
- break;
2282
- }
2283
- case PlacementAndUpdate: {
2284
- // Placement
2285
- commitPlacement(fiber);
2286
- // Clear the "placement" from effect tag so that we know that this is
2287
- // inserted, before any life-cycles like componentDidMount gets called.
2288
- fiber.flags &= ~Placement;
2289
-
2290
- // Update
2291
- const current = fiber.alternate;
2292
- commitWork(current, fiber);
2293
- break;
2294
- }
2295
- case Hydrating: {
2296
- fiber.flags &= ~Hydrating;
2297
- break;
2298
- }
2299
- case HydratingAndUpdate: {
2300
- fiber.flags &= ~Hydrating;
2301
-
2302
- // Update
2303
- const current = fiber.alternate;
2304
- commitWork(current, fiber);
2305
- break;
2306
- }
2307
- case Update: {
2308
- const current = fiber.alternate;
2309
- commitWork(current, fiber);
2310
- break;
2311
- }
2312
- }
2313
-}
2314
-
2315
-function commitMutationEffectsDeletions(
2316
- deletions: Array<Fiber>,
2317
- nearestMountedAncestor: Fiber,
2318
- root: FiberRoot,
2319
- renderPriorityLevel,
2320
-) {
2321
- for (let i = 0; i < deletions.length; i++) {
2322
- const childToDelete = deletions[i];
2323
- if (__DEV__) {
2324
- invokeGuardedCallback(
2325
- null,
2326
- commitDeletion,
2327
- null,
2328
- root,
2329
- childToDelete,
2330
- nearestMountedAncestor,
2331
- renderPriorityLevel,
2332
- );
2333
- if (hasCaughtError()) {
2334
- const error = clearCaughtError();
2335
- captureCommitPhaseError(childToDelete, nearestMountedAncestor, error);
2336
- }
2337
- } else {
2338
- try {
2339
- commitDeletion(
2340
- root,
2341
- childToDelete,
2342
- nearestMountedAncestor,
2343
- renderPriorityLevel,
2344
- );
2345
- } catch (error) {
2346
- captureCommitPhaseError(childToDelete, nearestMountedAncestor, error);
2347
- }
2348
- }
2349
- }
2350
-}
2351
-
2063
export function flushPassiveEffects(): boolean {
2064
// Returns whether passive effects were flushed.
2065
if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
@@ -2374,130 +2085,6 @@ export function flushPassiveEffects(): boolean {
2085
return false;
2086
}
2087
2377
-function flushPassiveMountEffects(root, firstChild: Fiber): void {
2378
- let fiber = firstChild;
2379
- while (fiber !== null) {
2380
- let prevProfilerOnStack = null;
2381
- if (enableProfilerTimer && enableProfilerCommitHooks) {
2382
- if (fiber.tag === Profiler) {
2383
- prevProfilerOnStack = nearestProfilerOnStack;
2384
- nearestProfilerOnStack = fiber;
2385
- }
2386
- }
2387
-
2388
- const primarySubtreeFlags = fiber.subtreeFlags & PassiveMask;
2389
-
2390
- if (fiber.child !== null && primarySubtreeFlags !== NoFlags) {
2391
- flushPassiveMountEffects(root, fiber.child);
2392
- }
2393
-
2394
- if ((fiber.flags & Passive) !== NoFlags) {
2395
- if (__DEV__) {
2396
- setCurrentDebugFiberInDEV(fiber);
2397
- invokeGuardedCallback(
2398
- null,
2399
- commitPassiveMountOnFiber,
2400
- null,
2401
- root,
2402
- fiber,
2403
- );
2404
- if (hasCaughtError()) {
2405
- const error = clearCaughtError();
2406
- captureCommitPhaseError(fiber, fiber.return, error);
2407
- }
2408
- resetCurrentDebugFiberInDEV();
2409
- } else {
2410
- try {
2411
- commitPassiveMountOnFiber(root, fiber);
2412
- } catch (error) {
2413
- captureCommitPhaseError(fiber, fiber.return, error);
2414
- }
2415
- }
2416
- }
2417
-
2418
- if (enableProfilerTimer && enableProfilerCommitHooks) {
2419
- if (fiber.tag === Profiler) {
2420
- // Bubble times to the next nearest ancestor Profiler.
2421
- // After we process that Profiler, we'll bubble further up.
2422
- if (prevProfilerOnStack !== null) {
2423
- prevProfilerOnStack.stateNode.passiveEffectDuration +=
2424
- fiber.stateNode.passiveEffectDuration;
2425
- }
2426
-
2427
- nearestProfilerOnStack = prevProfilerOnStack;
2428
- }
2429
- }
2430
-
2431
- fiber = fiber.sibling;
2432
- }
2433
-}
2434
-
2435
-function flushPassiveUnmountEffects(firstChild: Fiber): void {
2436
- let fiber = firstChild;
2437
- while (fiber !== null) {
2438
- const deletions = fiber.deletions;
2439
- if (deletions !== null) {
2440
- for (let i = 0; i < deletions.length; i++) {
2441
- const fiberToDelete = deletions[i];
2442
- flushPassiveUnmountEffectsInsideOfDeletedTree(fiberToDelete, fiber);
2443
-
2444
- // Now that passive effects have been processed, it's safe to detach lingering pointers.
2445
- detachFiberAfterEffects(fiberToDelete);
2446
- }
2447
- }
2448
-
2449
- const child = fiber.child;
2450
- if (child !== null) {
2451
- // If any children have passive effects then traverse the subtree.
2452
- // Note that this requires checking subtreeFlags of the current Fiber,
2453
- // rather than the subtreeFlags/effectsTag of the first child,
2454
- // since that would not cover passive effects in siblings.
2455
- const passiveFlags = fiber.subtreeFlags & PassiveMask;
2456
- if (passiveFlags !== NoFlags) {
2457
- flushPassiveUnmountEffects(child);
2458
- }
2459
- }
2460
-
2461
- const primaryFlags = fiber.flags & Passive;
2462
- if (primaryFlags !== NoFlags) {
2463
- setCurrentDebugFiberInDEV(fiber);
2464
- commitPassiveUnmountOnFiber(fiber);
2465
- resetCurrentDebugFiberInDEV();
2466
- }
2467
-
2468
- fiber = fiber.sibling;
2469
- }
2470
-}
2471
-
2472
-function flushPassiveUnmountEffectsInsideOfDeletedTree(
2473
- fiberToDelete: Fiber,
2474
- nearestMountedAncestor: Fiber,
2475
-): void {
2476
- if ((fiberToDelete.subtreeFlags & PassiveStatic) !== NoFlags) {
2477
- // If any children have passive effects then traverse the subtree.
2478
- // Note that this requires checking subtreeFlags of the current Fiber,
2479
- // rather than the subtreeFlags/effectsTag of the first child,
2480
- // since that would not cover passive effects in siblings.
2481
- let child = fiberToDelete.child;
2482
- while (child !== null) {
2483
- flushPassiveUnmountEffectsInsideOfDeletedTree(
2484
- child,
2485
- nearestMountedAncestor,
2486
- );
2487
- child = child.sibling;
2488
- }
2489
- }
2490
-
2491
- if ((fiberToDelete.flags & PassiveStatic) !== NoFlags) {
2492
- setCurrentDebugFiberInDEV(fiberToDelete);
2493
- commitPassiveUnmountInsideDeletedTreeOnFiber(
2494
- fiberToDelete,
2495
- nearestMountedAncestor,
2496
- );
2497
- resetCurrentDebugFiberInDEV();
2498
- }
2499
-}
2500
-
2088
function flushPassiveEffectsImpl() {
2089
if (rootWithPendingPassiveEffects === null) {
2090
return false;
@@ -2537,8 +2124,8 @@ function flushPassiveEffectsImpl() {
2124
// e.g. a destroy function in one component may unintentionally override a ref
2125
// value set by a create function in another component.
2126
// Layout effects have the same constraint.
2540
- flushPassiveUnmountEffects(root.current);
2541
- flushPassiveMountEffects(root, root.current);
2127
+ commitPassiveUnmountEffects(root.current);
2128
+ commitPassiveMountEffects(root, root.current);
2129
2130
if (__DEV__) {
2131
if (enableDebugTracing) {
@@ -2840,59 +2427,6 @@ function flushRenderPhaseStrictModeWarningsInDEV() {
2427
}
2428
}
2429
2843
-function commitDoubleInvokeEffectsInDEV(
2844
- fiber: Fiber,
2845
- hasPassiveEffects: boolean,
2846
-) {
2847
- if (__DEV__ && enableDoubleInvokingEffects) {
2848
- // Never double-invoke effects for legacy roots.
2849
- if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) {
2850
- return;
2851
- }
2852
-
2853
- setCurrentDebugFiberInDEV(fiber);
2854
- invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
2855
- if (hasPassiveEffects) {
2856
- invokeEffectsInDev(
2857
- fiber,
2858
- MountPassiveDev,
2859
- invokePassiveEffectUnmountInDEV,
2860
- );
2861
- }
2862
-
2863
- invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
2864
- if (hasPassiveEffects) {
2865
- invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
2866
- }
2867
- resetCurrentDebugFiberInDEV();
2868
- }
2869
-}
2870
-
2871
-function invokeEffectsInDev(
2872
- firstChild: Fiber,
2873
- fiberFlags: Flags,
2874
- invokeEffectFn: (fiber: Fiber) => void,
2875
-): void {
2876
- if (__DEV__ && enableDoubleInvokingEffects) {
2877
- // We don't need to re-check for legacy roots here.
2878
- // This function will not be called within legacy roots.
2879
- let fiber = firstChild;
2880
- while (fiber !== null) {
2881
- if (fiber.child !== null) {
2882
- const primarySubtreeFlag = fiber.subtreeFlags & fiberFlags;
2883
- if (primarySubtreeFlag !== NoFlags) {
2884
- invokeEffectsInDev(fiber.child, fiberFlags, invokeEffectFn);
2885
- }
2886
- }
2887
-
2888
- if ((fiber.flags & fiberFlags) !== NoFlags) {
2889
- invokeEffectFn(fiber);
2890
- }
2891
- fiber = fiber.sibling;
2892
- }
2893
- }
2894
-}
2895
-
2430
let didWarnStateUpdateForNotYetMountedComponent: Set<string> | null = null;
2431
function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) {
2432
if (__DEV__) {
@@ -3675,21 +3209,3 @@ export function act(callback: () => Thenable<mixed>): Thenable<void> {
3209
};
3210
}
3211
}
3678
-
3679
-function detachFiberAfterEffects(fiber: Fiber): void {
3680
- // Null out fields to improve GC for references that may be lingering (e.g. DevTools).
3681
- // Note that we already cleared the return pointer in detachFiberMutation().
3682
- fiber.child = null;
3683
- fiber.deletions = null;
3684
- fiber.dependencies = null;
3685
- fiber.memoizedProps = null;
3686
- fiber.memoizedState = null;
3687
- fiber.pendingProps = null;
3688
- fiber.sibling = null;
3689
- fiber.stateNode = null;
3690
- fiber.updateQueue = null;
3691
-
3692
- if (__DEV__) {
3693
- fiber._debugOwner = null;
3694
- }
3695
-}
packages/shared/ReactFeatureFlags.js
+2
@@ -136,3 +136,5 @@ export const enableDiscreteEventFlushingChange = false;
136
export const enableDoubleInvokingEffects = false;
137
138
export const enableUseRefAccessWarning = false;
139
+
140
+export const enableRecursiveCommitTraversal = false;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+2
@@ -53,6 +53,8 @@ export const enableDiscreteEventFlushingChange = false;
53
export const enableDoubleInvokingEffects = false;
54
export const enableUseRefAccessWarning = false;
55
56
+export const enableRecursiveCommitTraversal = false;
57
+
58
// Flow magic to verify the exports of this file match the original version.
59
// eslint-disable-next-line no-unused-vars
60
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+2
@@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = false;
52
export const enableDoubleInvokingEffects = false;
53
export const enableUseRefAccessWarning = false;
54
55
+export const enableRecursiveCommitTraversal = false;
56
+
57
// Flow magic to verify the exports of this file match the original version.
58
// eslint-disable-next-line no-unused-vars
59
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+2
@@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = false;
52
export const enableDoubleInvokingEffects = false;
53
export const enableUseRefAccessWarning = false;
54
55
+export const enableRecursiveCommitTraversal = false;
56
+
57
// Flow magic to verify the exports of this file match the original version.
58
// eslint-disable-next-line no-unused-vars
59
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+2
@@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = false;
52
export const enableDoubleInvokingEffects = false;
53
export const enableUseRefAccessWarning = false;
54
55
+export const enableRecursiveCommitTraversal = false;
56
+
57
// Flow magic to verify the exports of this file match the original version.
58
// eslint-disable-next-line no-unused-vars
59
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+2
@@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = false;
52
export const enableDoubleInvokingEffects = false;
53
export const enableUseRefAccessWarning = false;
54
55
+export const enableRecursiveCommitTraversal = false;
56
+
57
// Flow magic to verify the exports of this file match the original version.
58
// eslint-disable-next-line no-unused-vars
59
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.testing.js
+2
@@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = false;
52
export const enableDoubleInvokingEffects = false;
53
export const enableUseRefAccessWarning = false;
54
55
+export const enableRecursiveCommitTraversal = false;
56
+
57
// Flow magic to verify the exports of this file match the original version.
58
// eslint-disable-next-line no-unused-vars
59
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.testing.www.js
+2
@@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = true;
52
export const enableDoubleInvokingEffects = false;
53
export const enableUseRefAccessWarning = false;
54
55
+export const enableRecursiveCommitTraversal = false;
56
+
57
// Flow magic to verify the exports of this file match the original version.
58
// eslint-disable-next-line no-unused-vars
59
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.www.js
+2
@@ -84,6 +84,8 @@ export const enableDiscreteEventFlushingChange = true;
84
// to the correct value.
85
export const enableNewReconciler = __VARIANT__;
86
87
+export const enableRecursiveCommitTraversal = false;
88
+
89
// Flow magic to verify the exports of this file match the original version.
90
// eslint-disable-next-line no-unused-vars
91
type Check<_X, Y: _X, X: Y = _X> = null;