Convert layout phase to depth-first traversal (#20595)
Andrew Clark committed
Jan 15, 2021 at 14:22 UTC
6132919bf2b8851382547b34a442e7e0c09c5697
5 files changed
+368
-328
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+342
-249
@@ -69,8 +69,11 @@ import {
69
ChildDeletion,
70
Snapshot,
71
Update,
72
+ Callback,
73
+ Ref,
74
Passive,
75
PassiveMask,
76
+ LayoutMask,
77
PassiveUnmountPendingDev,
78
} from './ReactFiberFlags';
79
import getComponentName from 'shared/getComponentName';
@@ -490,95 +493,154 @@ export function commitPassiveEffectDurations(
493
}
494
}
495
493
-function commitLifeCycles(
496
+function commitLayoutEffectOnFiber(
497
finishedRoot: FiberRoot,
498
current: Fiber | null,
499
finishedWork: Fiber,
500
committedLanes: Lanes,
501
): void {
499
- switch (finishedWork.tag) {
500
- case FunctionComponent:
501
- case ForwardRef:
502
- case SimpleMemoComponent: {
503
- // At this point layout effects have already been destroyed (during mutation phase).
504
- // This is done to prevent sibling component effects from interfering with each other,
505
- // e.g. a destroy function in one component should never override a ref set
506
- // by a create function in another component during the same commit.
507
- if (
508
- enableProfilerTimer &&
509
- enableProfilerCommitHooks &&
510
- finishedWork.mode & ProfileMode
511
- ) {
512
- try {
513
- startLayoutEffectTimer();
502
+ if ((finishedWork.flags & (Update | Callback)) !== NoFlags) {
503
+ switch (finishedWork.tag) {
504
+ case FunctionComponent:
505
+ case ForwardRef:
506
+ case SimpleMemoComponent: {
507
+ // At this point layout effects have already been destroyed (during mutation phase).
508
+ // This is done to prevent sibling component effects from interfering with each other,
509
+ // e.g. a destroy function in one component should never override a ref set
510
+ // by a create function in another component during the same commit.
511
+ if (
512
+ enableProfilerTimer &&
513
+ enableProfilerCommitHooks &&
514
+ finishedWork.mode & ProfileMode
515
+ ) {
516
+ try {
517
+ startLayoutEffectTimer();
518
+ commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
519
+ } finally {
520
+ recordLayoutEffectDuration(finishedWork);
521
+ }
522
+ } else {
523
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
515
- } finally {
516
- recordLayoutEffectDuration(finishedWork);
524
}
518
- } else {
519
- commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
520
- }
525
522
- schedulePassiveEffects(finishedWork);
523
- return;
524
- }
525
- case ClassComponent: {
526
- const instance = finishedWork.stateNode;
527
- if (finishedWork.flags & Update) {
528
- if (current === null) {
529
- // We could update instance props and state here,
530
- // but instead we rely on them being set during last render.
531
- // TODO: revisit this when we implement resuming.
532
- if (__DEV__) {
526
+ schedulePassiveEffects(finishedWork);
527
+ break;
528
+ }
529
+ case ClassComponent: {
530
+ const instance = finishedWork.stateNode;
531
+ if (finishedWork.flags & Update) {
532
+ if (current === null) {
533
+ // We could update instance props and state here,
534
+ // but instead we rely on them being set during last render.
535
+ // TODO: revisit this when we implement resuming.
536
+ if (__DEV__) {
537
+ if (
538
+ finishedWork.type === finishedWork.elementType &&
539
+ !didWarnAboutReassigningProps
540
+ ) {
541
+ if (instance.props !== finishedWork.memoizedProps) {
542
+ console.error(
543
+ 'Expected %s props to match memoized props before ' +
544
+ 'componentDidMount. ' +
545
+ 'This might either be because of a bug in React, or because ' +
546
+ 'a component reassigns its own `this.props`. ' +
547
+ 'Please file an issue.',
548
+ getComponentName(finishedWork.type) || 'instance',
549
+ );
550
+ }
551
+ if (instance.state !== finishedWork.memoizedState) {
552
+ console.error(
553
+ 'Expected %s state to match memoized state before ' +
554
+ 'componentDidMount. ' +
555
+ 'This might either be because of a bug in React, or because ' +
556
+ 'a component reassigns its own `this.state`. ' +
557
+ 'Please file an issue.',
558
+ getComponentName(finishedWork.type) || 'instance',
559
+ );
560
+ }
561
+ }
562
+ }
563
if (
534
- finishedWork.type === finishedWork.elementType &&
535
- !didWarnAboutReassigningProps
564
+ enableProfilerTimer &&
565
+ enableProfilerCommitHooks &&
566
+ finishedWork.mode & ProfileMode
567
) {
537
- if (instance.props !== finishedWork.memoizedProps) {
538
- console.error(
539
- 'Expected %s props to match memoized props before ' +
540
- 'componentDidMount. ' +
541
- 'This might either be because of a bug in React, or because ' +
542
- 'a component reassigns its own `this.props`. ' +
543
- 'Please file an issue.',
544
- getComponentName(finishedWork.type) || 'instance',
545
- );
568
+ try {
569
+ startLayoutEffectTimer();
570
+ instance.componentDidMount();
571
+ } finally {
572
+ recordLayoutEffectDuration(finishedWork);
573
}
547
- if (instance.state !== finishedWork.memoizedState) {
548
- console.error(
549
- 'Expected %s state to match memoized state before ' +
550
- 'componentDidMount. ' +
551
- 'This might either be because of a bug in React, or because ' +
552
- 'a component reassigns its own `this.state`. ' +
553
- 'Please file an issue.',
554
- getComponentName(finishedWork.type) || 'instance',
555
- );
556
- }
557
- }
558
- }
559
- if (
560
- enableProfilerTimer &&
561
- enableProfilerCommitHooks &&
562
- finishedWork.mode & ProfileMode
563
- ) {
564
- try {
565
- startLayoutEffectTimer();
574
+ } else {
575
instance.componentDidMount();
567
- } finally {
568
- recordLayoutEffectDuration(finishedWork);
576
}
577
} else {
571
- instance.componentDidMount();
578
+ const prevProps =
579
+ finishedWork.elementType === finishedWork.type
580
+ ? current.memoizedProps
581
+ : resolveDefaultProps(finishedWork.type, current.memoizedProps);
582
+ const prevState = current.memoizedState;
583
+ // We could update instance props and state here,
584
+ // but instead we rely on them being set during last render.
585
+ // TODO: revisit this when we implement resuming.
586
+ if (__DEV__) {
587
+ if (
588
+ finishedWork.type === finishedWork.elementType &&
589
+ !didWarnAboutReassigningProps
590
+ ) {
591
+ if (instance.props !== finishedWork.memoizedProps) {
592
+ console.error(
593
+ 'Expected %s props to match memoized props before ' +
594
+ 'componentDidUpdate. ' +
595
+ 'This might either be because of a bug in React, or because ' +
596
+ 'a component reassigns its own `this.props`. ' +
597
+ 'Please file an issue.',
598
+ getComponentName(finishedWork.type) || 'instance',
599
+ );
600
+ }
601
+ if (instance.state !== finishedWork.memoizedState) {
602
+ console.error(
603
+ 'Expected %s state to match memoized state before ' +
604
+ 'componentDidUpdate. ' +
605
+ 'This might either be because of a bug in React, or because ' +
606
+ 'a component reassigns its own `this.state`. ' +
607
+ 'Please file an issue.',
608
+ getComponentName(finishedWork.type) || 'instance',
609
+ );
610
+ }
611
+ }
612
+ }
613
+ if (
614
+ enableProfilerTimer &&
615
+ enableProfilerCommitHooks &&
616
+ finishedWork.mode & ProfileMode
617
+ ) {
618
+ try {
619
+ startLayoutEffectTimer();
620
+ instance.componentDidUpdate(
621
+ prevProps,
622
+ prevState,
623
+ instance.__reactInternalSnapshotBeforeUpdate,
624
+ );
625
+ } finally {
626
+ recordLayoutEffectDuration(finishedWork);
627
+ }
628
+ } else {
629
+ instance.componentDidUpdate(
630
+ prevProps,
631
+ prevState,
632
+ instance.__reactInternalSnapshotBeforeUpdate,
633
+ );
634
+ }
635
}
573
- } else {
574
- const prevProps =
575
- finishedWork.elementType === finishedWork.type
576
- ? current.memoizedProps
577
- : resolveDefaultProps(finishedWork.type, current.memoizedProps);
578
- const prevState = current.memoizedState;
579
- // We could update instance props and state here,
580
- // but instead we rely on them being set during last render.
581
- // TODO: revisit this when we implement resuming.
636
+ }
637
+
638
+ // TODO: I think this is now always non-null by the time it reaches the
639
+ // commit phase. Consider removing the type check.
640
+ const updateQueue: UpdateQueue<
641
+ *,
642
+ > | null = (finishedWork.updateQueue: any);
643
+ if (updateQueue !== null) {
644
if (__DEV__) {
645
if (
646
finishedWork.type === finishedWork.elementType &&
@@ -587,7 +649,7 @@ function commitLifeCycles(
649
if (instance.props !== finishedWork.memoizedProps) {
650
console.error(
651
'Expected %s props to match memoized props before ' +
590
- 'componentDidUpdate. ' +
652
+ 'processing the update queue. ' +
653
'This might either be because of a bug in React, or because ' +
654
'a component reassigns its own `this.props`. ' +
655
'Please file an issue.',
@@ -597,7 +659,7 @@ function commitLifeCycles(
659
if (instance.state !== finishedWork.memoizedState) {
660
console.error(
661
'Expected %s state to match memoized state before ' +
600
- 'componentDidUpdate. ' +
662
+ 'processing the update queue. ' +
663
'This might either be because of a bug in React, or because ' +
664
'a component reassigns its own `this.state`. ' +
665
'Please file an issue.',
@@ -606,210 +668,166 @@ function commitLifeCycles(
668
}
669
}
670
}
609
- if (
610
- enableProfilerTimer &&
611
- enableProfilerCommitHooks &&
612
- finishedWork.mode & ProfileMode
613
- ) {
614
- try {
615
- startLayoutEffectTimer();
616
- instance.componentDidUpdate(
617
- prevProps,
618
- prevState,
619
- instance.__reactInternalSnapshotBeforeUpdate,
620
- );
621
- } finally {
622
- recordLayoutEffectDuration(finishedWork);
623
- }
624
- } else {
625
- instance.componentDidUpdate(
626
- prevProps,
627
- prevState,
628
- instance.__reactInternalSnapshotBeforeUpdate,
629
- );
630
- }
671
+ // We could update instance props and state here,
672
+ // but instead we rely on them being set during last render.
673
+ // TODO: revisit this when we implement resuming.
674
+ commitUpdateQueue(finishedWork, updateQueue, instance);
675
}
676
+ break;
677
}
633
-
634
- // TODO: I think this is now always non-null by the time it reaches the
635
- // commit phase. Consider removing the type check.
636
- const updateQueue: UpdateQueue<
637
- *,
638
- > | null = (finishedWork.updateQueue: any);
639
- if (updateQueue !== null) {
640
- if (__DEV__) {
641
- if (
642
- finishedWork.type === finishedWork.elementType &&
643
- !didWarnAboutReassigningProps
644
- ) {
645
- if (instance.props !== finishedWork.memoizedProps) {
646
- console.error(
647
- 'Expected %s props to match memoized props before ' +
648
- 'processing the update queue. ' +
649
- 'This might either be because of a bug in React, or because ' +
650
- 'a component reassigns its own `this.props`. ' +
651
- 'Please file an issue.',
652
- getComponentName(finishedWork.type) || 'instance',
653
- );
654
- }
655
- if (instance.state !== finishedWork.memoizedState) {
656
- console.error(
657
- 'Expected %s state to match memoized state before ' +
658
- 'processing the update queue. ' +
659
- 'This might either be because of a bug in React, or because ' +
660
- 'a component reassigns its own `this.state`. ' +
661
- 'Please file an issue.',
662
- getComponentName(finishedWork.type) || 'instance',
663
- );
678
+ case HostRoot: {
679
+ // TODO: I think this is now always non-null by the time it reaches the
680
+ // commit phase. Consider removing the type check.
681
+ const updateQueue: UpdateQueue<
682
+ *,
683
+ > | null = (finishedWork.updateQueue: any);
684
+ if (updateQueue !== null) {
685
+ let instance = null;
686
+ if (finishedWork.child !== null) {
687
+ switch (finishedWork.child.tag) {
688
+ case HostComponent:
689
+ instance = getPublicInstance(finishedWork.child.stateNode);
690
+ break;
691
+ case ClassComponent:
692
+ instance = finishedWork.child.stateNode;
693
+ break;
694
}
695
}
696
+ commitUpdateQueue(finishedWork, updateQueue, instance);
697
}
667
- // We could update instance props and state here,
668
- // but instead we rely on them being set during last render.
669
- // TODO: revisit this when we implement resuming.
670
- commitUpdateQueue(finishedWork, updateQueue, instance);
698
+ break;
699
}
672
- return;
673
- }
674
- case HostRoot: {
675
- // TODO: I think this is now always non-null by the time it reaches the
676
- // commit phase. Consider removing the type check.
677
- const updateQueue: UpdateQueue<
678
- *,
679
- > | null = (finishedWork.updateQueue: any);
680
- if (updateQueue !== null) {
681
- let instance = null;
682
- if (finishedWork.child !== null) {
683
- switch (finishedWork.child.tag) {
684
- case HostComponent:
685
- instance = getPublicInstance(finishedWork.child.stateNode);
686
- break;
687
- case ClassComponent:
688
- instance = finishedWork.child.stateNode;
689
- break;
690
- }
700
+ case HostComponent: {
701
+ const instance: Instance = finishedWork.stateNode;
702
+
703
+ // Renderers may schedule work to be done after host components are mounted
704
+ // (eg DOM renderer may schedule auto-focus for inputs and form controls).
705
+ // These effects should only be committed when components are first mounted,
706
+ // aka when there is no current/alternate.
707
+ if (current === null && finishedWork.flags & Update) {
708
+ const type = finishedWork.type;
709
+ const props = finishedWork.memoizedProps;
710
+ commitMount(instance, type, props, finishedWork);
711
}
692
- commitUpdateQueue(finishedWork, updateQueue, instance);
693
- }
694
- return;
695
- }
696
- case HostComponent: {
697
- const instance: Instance = finishedWork.stateNode;
712
699
- // Renderers may schedule work to be done after host components are mounted
700
- // (eg DOM renderer may schedule auto-focus for inputs and form controls).
701
- // These effects should only be committed when components are first mounted,
702
- // aka when there is no current/alternate.
703
- if (current === null && finishedWork.flags & Update) {
704
- const type = finishedWork.type;
705
- const props = finishedWork.memoizedProps;
706
- commitMount(instance, type, props, finishedWork);
713
+ break;
714
}
715
+ case HostText: {
716
+ // We have no life-cycles associated with text.
717
+ break;
718
+ }
719
+ case HostPortal: {
720
+ // We have no life-cycles associated with portals.
721
+ break;
722
+ }
723
+ case Profiler: {
724
+ if (enableProfilerTimer) {
725
+ const {onCommit, onRender} = finishedWork.memoizedProps;
726
+ const {effectDuration} = finishedWork.stateNode;
727
709
- return;
710
- }
711
- case HostText: {
712
- // We have no life-cycles associated with text.
713
- return;
714
- }
715
- case HostPortal: {
716
- // We have no life-cycles associated with portals.
717
- return;
718
- }
719
- case Profiler: {
720
- if (enableProfilerTimer) {
721
- const {onCommit, onRender} = finishedWork.memoizedProps;
722
- const {effectDuration} = finishedWork.stateNode;
723
-
724
- const commitTime = getCommitTime();
725
-
726
- let phase = current === null ? 'mount' : 'update';
727
- if (enableProfilerNestedUpdatePhase) {
728
- if (isCurrentUpdateNested()) {
729
- phase = 'nested-update';
730
- }
731
- }
728
+ const commitTime = getCommitTime();
729
733
- if (typeof onRender === 'function') {
734
- if (enableSchedulerTracing) {
735
- onRender(
736
- finishedWork.memoizedProps.id,
737
- phase,
738
- finishedWork.actualDuration,
739
- finishedWork.treeBaseDuration,
740
- finishedWork.actualStartTime,
741
- commitTime,
742
- finishedRoot.memoizedInteractions,
743
- );
744
- } else {
745
- onRender(
746
- finishedWork.memoizedProps.id,
747
- phase,
748
- finishedWork.actualDuration,
749
- finishedWork.treeBaseDuration,
750
- finishedWork.actualStartTime,
751
- commitTime,
752
- );
730
+ let phase = current === null ? 'mount' : 'update';
731
+ if (enableProfilerNestedUpdatePhase) {
732
+ if (isCurrentUpdateNested()) {
733
+ phase = 'nested-update';
734
+ }
735
}
754
- }
736
756
- if (enableProfilerCommitHooks) {
757
- if (typeof onCommit === 'function') {
737
+ if (typeof onRender === 'function') {
738
if (enableSchedulerTracing) {
759
- onCommit(
739
+ onRender(
740
finishedWork.memoizedProps.id,
741
phase,
762
- effectDuration,
742
+ finishedWork.actualDuration,
743
+ finishedWork.treeBaseDuration,
744
+ finishedWork.actualStartTime,
745
commitTime,
746
finishedRoot.memoizedInteractions,
747
);
748
} else {
767
- onCommit(
749
+ onRender(
750
finishedWork.memoizedProps.id,
751
phase,
770
- effectDuration,
752
+ finishedWork.actualDuration,
753
+ finishedWork.treeBaseDuration,
754
+ finishedWork.actualStartTime,
755
commitTime,
756
);
757
}
758
}
759
776
- // Schedule a passive effect for this Profiler to call onPostCommit hooks.
777
- // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
778
- // because the effect is also where times bubble to parent Profilers.
779
- enqueuePendingPassiveProfilerEffect(finishedWork);
760
+ if (enableProfilerCommitHooks) {
761
+ if (typeof onCommit === 'function') {
762
+ if (enableSchedulerTracing) {
763
+ onCommit(
764
+ finishedWork.memoizedProps.id,
765
+ phase,
766
+ effectDuration,
767
+ commitTime,
768
+ finishedRoot.memoizedInteractions,
769
+ );
770
+ } else {
771
+ onCommit(
772
+ finishedWork.memoizedProps.id,
773
+ phase,
774
+ effectDuration,
775
+ commitTime,
776
+ );
777
+ }
778
+ }
779
781
- // Propagate layout effect durations to the next nearest Profiler ancestor.
782
- // Do not reset these values until the next render so DevTools has a chance to read them first.
783
- let parentFiber = finishedWork.return;
784
- while (parentFiber !== null) {
785
- if (parentFiber.tag === Profiler) {
786
- const parentStateNode = parentFiber.stateNode;
787
- parentStateNode.effectDuration += effectDuration;
788
- break;
780
+ // Schedule a passive effect for this Profiler to call onPostCommit hooks.
781
+ // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
782
+ // because the effect is also where times bubble to parent Profilers.
783
+ enqueuePendingPassiveProfilerEffect(finishedWork);
784
+
785
+ // Propagate layout effect durations to the next nearest Profiler ancestor.
786
+ // Do not reset these values until the next render so DevTools has a chance to read them first.
787
+ let parentFiber = finishedWork.return;
788
+ while (parentFiber !== null) {
789
+ if (parentFiber.tag === Profiler) {
790
+ const parentStateNode = parentFiber.stateNode;
791
+ parentStateNode.effectDuration += effectDuration;
792
+ break;
793
+ }
794
+ parentFiber = parentFiber.return;
795
}
790
- parentFiber = parentFiber.return;
796
}
797
}
798
+ break;
799
}
794
- return;
800
+ case SuspenseComponent: {
801
+ commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
802
+ break;
803
+ }
804
+ case SuspenseListComponent:
805
+ case IncompleteClassComponent:
806
+ case FundamentalComponent:
807
+ case ScopeComponent:
808
+ case OffscreenComponent:
809
+ case LegacyHiddenComponent:
810
+ break;
811
+ default:
812
+ invariant(
813
+ false,
814
+ 'This unit of work tag should not have side-effects. This error is ' +
815
+ 'likely caused by a bug in React. Please file an issue.',
816
+ );
817
}
796
- case SuspenseComponent: {
797
- commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
798
- return;
818
+ }
819
+
820
+ if (enableScopeAPI) {
821
+ // TODO: This is a temporary solution that allowed us to transition away
822
+ // from React Flare on www.
823
+ if (finishedWork.flags & Ref && finishedWork.tag !== ScopeComponent) {
824
+ commitAttachRef(finishedWork);
825
+ }
826
+ } else {
827
+ if (finishedWork.flags & Ref) {
828
+ commitAttachRef(finishedWork);
829
}
800
- case SuspenseListComponent:
801
- case IncompleteClassComponent:
802
- case FundamentalComponent:
803
- case ScopeComponent:
804
- case OffscreenComponent:
805
- case LegacyHiddenComponent:
806
- return;
830
}
808
- invariant(
809
- false,
810
- 'This unit of work tag should not have side-effects. This error is ' +
811
- 'likely caused by a bug in React. Please file an issue.',
812
- );
831
}
832
833
function hideOrUnhideAllChildren(finishedWork, isHidden) {
@@ -1823,12 +1841,88 @@ function commitResetTextContent(current: Fiber) {
1841
resetTextContent(current.stateNode);
1842
}
1843
1844
+export function commitLayoutEffects(
1845
+ finishedWork: Fiber,
1846
+ root: FiberRoot,
1847
+ committedLanes: Lanes,
1848
+): void {
1849
+ nextEffect = finishedWork;
1850
+ commitLayoutEffects_begin(finishedWork, root, committedLanes);
1851
+}
1852
+
1853
+function commitLayoutEffects_begin(
1854
+ subtreeRoot: Fiber,
1855
+ root: FiberRoot,
1856
+ committedLanes: Lanes,
1857
+) {
1858
+ while (nextEffect !== null) {
1859
+ const fiber = nextEffect;
1860
+ const firstChild = fiber.child;
1861
+ if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) {
1862
+ ensureCorrectReturnPointer(firstChild, fiber);
1863
+ nextEffect = firstChild;
1864
+ } else {
1865
+ commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
1866
+ }
1867
+ }
1868
+}
1869
+
1870
+function commitLayoutMountEffects_complete(
1871
+ subtreeRoot: Fiber,
1872
+ root: FiberRoot,
1873
+ committedLanes: Lanes,
1874
+) {
1875
+ while (nextEffect !== null) {
1876
+ const fiber = nextEffect;
1877
+ if ((fiber.flags & LayoutMask) !== NoFlags) {
1878
+ const current = fiber.alternate;
1879
+ if (__DEV__) {
1880
+ setCurrentDebugFiberInDEV(fiber);
1881
+ invokeGuardedCallback(
1882
+ null,
1883
+ commitLayoutEffectOnFiber,
1884
+ null,
1885
+ root,
1886
+ current,
1887
+ fiber,
1888
+ committedLanes,
1889
+ );
1890
+ if (hasCaughtError()) {
1891
+ const error = clearCaughtError();
1892
+ captureCommitPhaseError(fiber, error);
1893
+ }
1894
+ resetCurrentDebugFiberInDEV();
1895
+ } else {
1896
+ try {
1897
+ commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
1898
+ } catch (error) {
1899
+ captureCommitPhaseError(fiber, error);
1900
+ }
1901
+ }
1902
+ }
1903
+
1904
+ if (fiber === subtreeRoot) {
1905
+ nextEffect = null;
1906
+ return;
1907
+ }
1908
+
1909
+ const sibling = fiber.sibling;
1910
+ if (sibling !== null) {
1911
+ ensureCorrectReturnPointer(sibling, fiber.return);
1912
+ nextEffect = sibling;
1913
+ return;
1914
+ }
1915
+
1916
+ nextEffect = fiber.return;
1917
+ }
1918
+}
1919
+
1920
export function commitPassiveMountEffects(
1921
root: FiberRoot,
1828
- firstChild: Fiber,
1922
+ finishedWork: Fiber,
1923
): void {
1830
- nextEffect = firstChild;
1831
- commitPassiveMountEffects_begin(firstChild, root);
1924
+ nextEffect = finishedWork;
1925
+ commitPassiveMountEffects_begin(finishedWork, root);
1926
}
1927
1928
function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) {
@@ -2094,7 +2188,6 @@ export {
2188
commitPlacement,
2189
commitDeletion,
2190
commitWork,
2097
- commitLifeCycles,
2191
commitAttachRef,
2192
commitDetachRef,
2193
};
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+17
-71
@@ -131,7 +131,6 @@ import {
131
Ref,
132
ContentReset,
133
Snapshot,
134
- Callback,
134
Passive,
135
PassiveStatic,
136
Incomplete,
@@ -190,7 +189,7 @@ import {
189
} from './ReactFiberThrow.new';
190
import {
191
commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
193
- commitLifeCycles as commitLayoutEffectOnFiber,
192
+ commitLayoutEffects,
193
commitPlacement,
194
commitWork,
195
commitDeletion,
@@ -2073,28 +2072,24 @@ function commitRootImpl(root, renderPriorityLevel) {
2072
// The next phase is the layout phase, where we call effects that read
2073
// the host tree after it's been mutated. The idiomatic use case for this is
2074
// layout, but class component lifecycles also fire here for legacy reasons.
2076
- nextEffect = firstEffect;
2077
- do {
2078
- if (__DEV__) {
2079
- invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
2080
- if (hasCaughtError()) {
2081
- invariant(nextEffect !== null, 'Should be working on an effect.');
2082
- const error = clearCaughtError();
2083
- captureCommitPhaseError(nextEffect, error);
2084
- nextEffect = nextEffect.nextEffect;
2085
- }
2086
- } else {
2087
- try {
2088
- commitLayoutEffects(root, lanes);
2089
- } catch (error) {
2090
- invariant(nextEffect !== null, 'Should be working on an effect.');
2091
- captureCommitPhaseError(nextEffect, error);
2092
- nextEffect = nextEffect.nextEffect;
2093
- }
2075
+ if (__DEV__) {
2076
+ if (enableDebugTracing) {
2077
+ logLayoutEffectsStarted(lanes);
2078
}
2095
- } while (nextEffect !== null);
2079
+ }
2080
+ if (enableSchedulingProfiler) {
2081
+ markLayoutEffectsStarted(lanes);
2082
+ }
2083
+ commitLayoutEffects(finishedWork, root, lanes);
2084
+ if (__DEV__) {
2085
+ if (enableDebugTracing) {
2086
+ logLayoutEffectsStopped();
2087
+ }
2088
+ }
2089
2097
- nextEffect = null;
2090
+ if (enableSchedulingProfiler) {
2091
+ markLayoutEffectsStopped();
2092
+ }
2093
2094
if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
2095
rootCommittingMutationOrLayoutEffects = null;
@@ -2420,55 +2415,6 @@ function commitMutationEffects(
2415
}
2416
}
2417
2423
-function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
2424
- if (__DEV__) {
2425
- if (enableDebugTracing) {
2426
- logLayoutEffectsStarted(committedLanes);
2427
- }
2428
- }
2429
-
2430
- if (enableSchedulingProfiler) {
2431
- markLayoutEffectsStarted(committedLanes);
2432
- }
2433
-
2434
- // TODO: Should probably move the bulk of this function to commitWork.
2435
- while (nextEffect !== null) {
2436
- setCurrentDebugFiberInDEV(nextEffect);
2437
-
2438
- const flags = nextEffect.flags;
2439
-
2440
- if (flags & (Update | Callback)) {
2441
- const current = nextEffect.alternate;
2442
- commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
2443
- }
2444
-
2445
- if (enableScopeAPI) {
2446
- // TODO: This is a temporary solution that allowed us to transition away
2447
- // from React Flare on www.
2448
- if (flags & Ref && nextEffect.tag !== ScopeComponent) {
2449
- commitAttachRef(nextEffect);
2450
- }
2451
- } else {
2452
- if (flags & Ref) {
2453
- commitAttachRef(nextEffect);
2454
- }
2455
- }
2456
-
2457
- resetCurrentDebugFiberInDEV();
2458
- nextEffect = nextEffect.nextEffect;
2459
- }
2460
-
2461
- if (__DEV__) {
2462
- if (enableDebugTracing) {
2463
- logLayoutEffectsStopped();
2464
- }
2465
- }
2466
-
2467
- if (enableSchedulingProfiler) {
2468
- markLayoutEffectsStopped();
2469
- }
2470
-}
2471
-
2418
export function flushPassiveEffects(): boolean {
2419
// Returns whether passive effects were flushed.
2420
if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
packages/react/src/__tests__/ReactDOMTracing-test.internal.js
+2
-2
@@ -152,7 +152,7 @@ describe('ReactDOMTracing', () => {
152
onInteractionScheduledWorkCompleted,
153
).toHaveBeenLastNotifiedOfInteraction(interaction);
154
155
- if (gate(flags => flags.dfsEffectsRefactor)) {
155
+ if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) {
156
expect(onRender).toHaveBeenCalledTimes(3);
157
} else {
158
// TODO: This is 4 instead of 3 because this update was scheduled at
@@ -310,7 +310,7 @@ describe('ReactDOMTracing', () => {
310
expect(
311
onInteractionScheduledWorkCompleted,
312
).toHaveBeenLastNotifiedOfInteraction(interaction);
313
- if (gate(flags => flags.dfsEffectsRefactor)) {
313
+ if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) {
314
expect(onRender).toHaveBeenCalledTimes(3);
315
} else {
316
// TODO: This is 4 instead of 3 because this update was scheduled at
packages/react/src/__tests__/ReactProfiler-test.internal.js
+3
-3
@@ -368,7 +368,7 @@ describe('Profiler', () => {
368
369
renderer.update(<App />);
370
371
- if (gate(flags => flags.dfsEffectsRefactor)) {
371
+ if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) {
372
// None of the Profiler's subtree was rendered because App bailed out before the Profiler.
373
// So we expect onRender not to be called.
374
expect(callback).not.toHaveBeenCalled();
@@ -4383,7 +4383,7 @@ describe('Profiler', () => {
4383
// because the resolved suspended subtree doesn't contain any passive effects.
4384
// If <AsyncComponentWithCascadingWork> or its decendents had a passive effect,
4385
// onPostCommit would be called again.
4386
- if (gate(flags => flags.dfsEffectsRefactor)) {
4386
+ if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) {
4387
expect(Scheduler).toFlushAndYield([]);
4388
} else {
4389
expect(Scheduler).toFlushAndYield(['onPostCommit']);
@@ -4874,7 +4874,7 @@ describe('Profiler', () => {
4874
});
4875
4876
if (__DEV__) {
4877
- // @gate dfsEffectsRefactor
4877
+ // @gate enableUseJSStackToTrackPassiveDurations
4878
// @gate enableDoubleInvokingEffects
4879
it('double invoking does not disconnect wrapped async work', () => {
4880
ReactFeatureFlags.enableDoubleInvokingEffects = true;
scripts/jest/TestFlags.js
+4
-3
@@ -45,9 +45,10 @@ const environmentFlags = {
45
// Use this for tests that are known to be broken.
46
FIXME: false,
47
48
- // Turn this flag back on (or delete) once the effect list is removed in favor
49
- // of a depth-first traversal using `subtreeTags`.
50
- dfsEffectsRefactor: false,
48
+ // Turn these flags back on (or delete) once the effect list is removed in
49
+ // favor of a depth-first traversal using `subtreeTags`.
50
+ dfsEffectsRefactor: __VARIANT__,
51
+ enableUseJSStackToTrackPassiveDurations: false,
52
};
53
54
function getTestFlags() {