Extract layout effects to separate functions
There's a lot of duplicated code between commitLayoutEffectOnFiber and the "reappear layout effects" path that happens when an Offscreen tree goes from hidden back to visible. I'm going to refactor these to share more of the same code. As a first step, this extracts the shared parts into separate functions. This may not save much on code size because Closure will likely inline some of it, anyway, but it makes it harder for the two paths to accidentally diverge.
Andrew Clark committed
Jul 14, 2022 at 12:29 UTC
679eea3282967742473910aed1209ac8c21f2a67
2 files changed
+588
-606
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+294
-303
@@ -706,6 +706,272 @@ export function commitPassiveEffectDurations(
706
}
707
}
708
709
+function commitHookLayoutEffects(finishedWork: Fiber) {
710
+ // At this point layout effects have already been destroyed (during mutation phase).
711
+ // This is done to prevent sibling component effects from interfering with each other,
712
+ // e.g. a destroy function in one component should never override a ref set
713
+ // by a create function in another component during the same commit.
714
+ if (
715
+ enableProfilerTimer &&
716
+ enableProfilerCommitHooks &&
717
+ finishedWork.mode & ProfileMode
718
+ ) {
719
+ try {
720
+ startLayoutEffectTimer();
721
+ commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
722
+ } catch (error) {
723
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
724
+ }
725
+ recordLayoutEffectDuration(finishedWork);
726
+ } else {
727
+ try {
728
+ commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
729
+ } catch (error) {
730
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
731
+ }
732
+ }
733
+}
734
+
735
+function commitClassLayoutLifecycles(
736
+ finishedWork: Fiber,
737
+ current: Fiber | null,
738
+) {
739
+ const instance = finishedWork.stateNode;
740
+ if (current === null) {
741
+ // We could update instance props and state here,
742
+ // but instead we rely on them being set during last render.
743
+ // TODO: revisit this when we implement resuming.
744
+ if (__DEV__) {
745
+ if (
746
+ finishedWork.type === finishedWork.elementType &&
747
+ !didWarnAboutReassigningProps
748
+ ) {
749
+ if (instance.props !== finishedWork.memoizedProps) {
750
+ console.error(
751
+ 'Expected %s props to match memoized props before ' +
752
+ 'componentDidMount. ' +
753
+ 'This might either be because of a bug in React, or because ' +
754
+ 'a component reassigns its own `this.props`. ' +
755
+ 'Please file an issue.',
756
+ getComponentNameFromFiber(finishedWork) || 'instance',
757
+ );
758
+ }
759
+ if (instance.state !== finishedWork.memoizedState) {
760
+ console.error(
761
+ 'Expected %s state to match memoized state before ' +
762
+ 'componentDidMount. ' +
763
+ 'This might either be because of a bug in React, or because ' +
764
+ 'a component reassigns its own `this.state`. ' +
765
+ 'Please file an issue.',
766
+ getComponentNameFromFiber(finishedWork) || 'instance',
767
+ );
768
+ }
769
+ }
770
+ }
771
+ if (
772
+ enableProfilerTimer &&
773
+ enableProfilerCommitHooks &&
774
+ finishedWork.mode & ProfileMode
775
+ ) {
776
+ try {
777
+ startLayoutEffectTimer();
778
+ instance.componentDidMount();
779
+ } catch (error) {
780
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
781
+ }
782
+ recordLayoutEffectDuration(finishedWork);
783
+ } else {
784
+ try {
785
+ instance.componentDidMount();
786
+ } catch (error) {
787
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
788
+ }
789
+ }
790
+ } else {
791
+ const prevProps =
792
+ finishedWork.elementType === finishedWork.type
793
+ ? current.memoizedProps
794
+ : resolveDefaultProps(finishedWork.type, current.memoizedProps);
795
+ const prevState = current.memoizedState;
796
+ // We could update instance props and state here,
797
+ // but instead we rely on them being set during last render.
798
+ // TODO: revisit this when we implement resuming.
799
+ if (__DEV__) {
800
+ if (
801
+ finishedWork.type === finishedWork.elementType &&
802
+ !didWarnAboutReassigningProps
803
+ ) {
804
+ if (instance.props !== finishedWork.memoizedProps) {
805
+ console.error(
806
+ 'Expected %s props to match memoized props before ' +
807
+ 'componentDidUpdate. ' +
808
+ 'This might either be because of a bug in React, or because ' +
809
+ 'a component reassigns its own `this.props`. ' +
810
+ 'Please file an issue.',
811
+ getComponentNameFromFiber(finishedWork) || 'instance',
812
+ );
813
+ }
814
+ if (instance.state !== finishedWork.memoizedState) {
815
+ console.error(
816
+ 'Expected %s state to match memoized state before ' +
817
+ 'componentDidUpdate. ' +
818
+ 'This might either be because of a bug in React, or because ' +
819
+ 'a component reassigns its own `this.state`. ' +
820
+ 'Please file an issue.',
821
+ getComponentNameFromFiber(finishedWork) || 'instance',
822
+ );
823
+ }
824
+ }
825
+ }
826
+ if (
827
+ enableProfilerTimer &&
828
+ enableProfilerCommitHooks &&
829
+ finishedWork.mode & ProfileMode
830
+ ) {
831
+ try {
832
+ startLayoutEffectTimer();
833
+ instance.componentDidUpdate(
834
+ prevProps,
835
+ prevState,
836
+ instance.__reactInternalSnapshotBeforeUpdate,
837
+ );
838
+ } catch (error) {
839
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
840
+ }
841
+ recordLayoutEffectDuration(finishedWork);
842
+ } else {
843
+ try {
844
+ instance.componentDidUpdate(
845
+ prevProps,
846
+ prevState,
847
+ instance.__reactInternalSnapshotBeforeUpdate,
848
+ );
849
+ } catch (error) {
850
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
851
+ }
852
+ }
853
+ }
854
+}
855
+
856
+function commitClassCallbacks(finishedWork: Fiber, current: Fiber | null) {
857
+ // TODO: I think this is now always non-null by the time it reaches the
858
+ // commit phase. Consider removing the type check.
859
+ const updateQueue: UpdateQueue<*> | null = (finishedWork.updateQueue: any);
860
+ if (updateQueue !== null) {
861
+ const instance = finishedWork.stateNode;
862
+ if (__DEV__) {
863
+ if (
864
+ finishedWork.type === finishedWork.elementType &&
865
+ !didWarnAboutReassigningProps
866
+ ) {
867
+ if (instance.props !== finishedWork.memoizedProps) {
868
+ console.error(
869
+ 'Expected %s props to match memoized props before ' +
870
+ 'processing the update queue. ' +
871
+ 'This might either be because of a bug in React, or because ' +
872
+ 'a component reassigns its own `this.props`. ' +
873
+ 'Please file an issue.',
874
+ getComponentNameFromFiber(finishedWork) || 'instance',
875
+ );
876
+ }
877
+ if (instance.state !== finishedWork.memoizedState) {
878
+ console.error(
879
+ 'Expected %s state to match memoized state before ' +
880
+ 'processing the update queue. ' +
881
+ 'This might either be because of a bug in React, or because ' +
882
+ 'a component reassigns its own `this.state`. ' +
883
+ 'Please file an issue.',
884
+ getComponentNameFromFiber(finishedWork) || 'instance',
885
+ );
886
+ }
887
+ }
888
+ }
889
+ // We could update instance props and state here,
890
+ // but instead we rely on them being set during last render.
891
+ // TODO: revisit this when we implement resuming.
892
+ try {
893
+ commitCallbacks(updateQueue, instance);
894
+ } catch (error) {
895
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
896
+ }
897
+ }
898
+}
899
+
900
+function commitHostComponentMount(finishedWork: Fiber, current: Fiber | null) {
901
+ const type = finishedWork.type;
902
+ const props = finishedWork.memoizedProps;
903
+ const instance: Instance = finishedWork.stateNode;
904
+ try {
905
+ commitMount(instance, type, props, finishedWork);
906
+ } catch (error) {
907
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
908
+ }
909
+}
910
+
911
+function commitProfilerUpdate(finishedWork: Fiber, current: Fiber | null) {
912
+ if (enableProfilerTimer) {
913
+ try {
914
+ const {onCommit, onRender} = finishedWork.memoizedProps;
915
+ const {effectDuration} = finishedWork.stateNode;
916
+
917
+ const commitTime = getCommitTime();
918
+
919
+ let phase = current === null ? 'mount' : 'update';
920
+ if (enableProfilerNestedUpdatePhase) {
921
+ if (isCurrentUpdateNested()) {
922
+ phase = 'nested-update';
923
+ }
924
+ }
925
+
926
+ if (typeof onRender === 'function') {
927
+ onRender(
928
+ finishedWork.memoizedProps.id,
929
+ phase,
930
+ finishedWork.actualDuration,
931
+ finishedWork.treeBaseDuration,
932
+ finishedWork.actualStartTime,
933
+ commitTime,
934
+ );
935
+ }
936
+
937
+ if (enableProfilerCommitHooks) {
938
+ if (typeof onCommit === 'function') {
939
+ onCommit(
940
+ finishedWork.memoizedProps.id,
941
+ phase,
942
+ effectDuration,
943
+ commitTime,
944
+ );
945
+ }
946
+
947
+ // Schedule a passive effect for this Profiler to call onPostCommit hooks.
948
+ // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
949
+ // because the effect is also where times bubble to parent Profilers.
950
+ enqueuePendingPassiveProfilerEffect(finishedWork);
951
+
952
+ // Propagate layout effect durations to the next nearest Profiler ancestor.
953
+ // Do not reset these values until the next render so DevTools has a chance to read them first.
954
+ let parentFiber = finishedWork.return;
955
+ outer: while (parentFiber !== null) {
956
+ switch (parentFiber.tag) {
957
+ case HostRoot:
958
+ const root = parentFiber.stateNode;
959
+ root.effectDuration += effectDuration;
960
+ break outer;
961
+ case Profiler:
962
+ const parentStateNode = parentFiber.stateNode;
963
+ parentStateNode.effectDuration += effectDuration;
964
+ break outer;
965
+ }
966
+ parentFiber = parentFiber.return;
967
+ }
968
+ }
969
+ } catch (error) {
970
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
971
+ }
972
+ }
973
+}
974
+
975
function commitLayoutEffectOnFiber(
976
finishedRoot: FiberRoot,
977
current: Fiber | null,
@@ -724,35 +990,7 @@ function commitLayoutEffectOnFiber(
990
);
991
if (flags & Update) {
992
if (!offscreenSubtreeWasHidden) {
727
- // At this point layout effects have already been destroyed (during mutation phase).
728
- // This is done to prevent sibling component effects from interfering with each other,
729
- // e.g. a destroy function in one component should never override a ref set
730
- // by a create function in another component during the same commit.
731
- if (
732
- enableProfilerTimer &&
733
- enableProfilerCommitHooks &&
734
- finishedWork.mode & ProfileMode
735
- ) {
736
- try {
737
- startLayoutEffectTimer();
738
- commitHookEffectListMount(
739
- HookLayout | HookHasEffect,
740
- finishedWork,
741
- );
742
- } catch (error) {
743
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
744
- }
745
- recordLayoutEffectDuration(finishedWork);
746
- } else {
747
- try {
748
- commitHookEffectListMount(
749
- HookLayout | HookHasEffect,
750
- finishedWork,
751
- );
752
- } catch (error) {
753
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
754
- }
755
- }
993
+ commitHookLayoutEffects(finishedWork);
994
}
995
}
996
break;
@@ -765,193 +1003,17 @@ function commitLayoutEffectOnFiber(
1003
);
1004
if (flags & Update) {
1005
if (!offscreenSubtreeWasHidden) {
768
- const instance = finishedWork.stateNode;
769
- if (current === null) {
770
- // We could update instance props and state here,
771
- // but instead we rely on them being set during last render.
772
- // TODO: revisit this when we implement resuming.
773
- if (__DEV__) {
774
- if (
775
- finishedWork.type === finishedWork.elementType &&
776
- !didWarnAboutReassigningProps
777
- ) {
778
- if (instance.props !== finishedWork.memoizedProps) {
779
- console.error(
780
- 'Expected %s props to match memoized props before ' +
781
- 'componentDidMount. ' +
782
- 'This might either be because of a bug in React, or because ' +
783
- 'a component reassigns its own `this.props`. ' +
784
- 'Please file an issue.',
785
- getComponentNameFromFiber(finishedWork) || 'instance',
786
- );
787
- }
788
- if (instance.state !== finishedWork.memoizedState) {
789
- console.error(
790
- 'Expected %s state to match memoized state before ' +
791
- 'componentDidMount. ' +
792
- 'This might either be because of a bug in React, or because ' +
793
- 'a component reassigns its own `this.state`. ' +
794
- 'Please file an issue.',
795
- getComponentNameFromFiber(finishedWork) || 'instance',
796
- );
797
- }
798
- }
799
- }
800
- if (
801
- enableProfilerTimer &&
802
- enableProfilerCommitHooks &&
803
- finishedWork.mode & ProfileMode
804
- ) {
805
- try {
806
- startLayoutEffectTimer();
807
- instance.componentDidMount();
808
- } catch (error) {
809
- captureCommitPhaseError(
810
- finishedWork,
811
- finishedWork.return,
812
- error,
813
- );
814
- }
815
- recordLayoutEffectDuration(finishedWork);
816
- } else {
817
- try {
818
- instance.componentDidMount();
819
- } catch (error) {
820
- captureCommitPhaseError(
821
- finishedWork,
822
- finishedWork.return,
823
- error,
824
- );
825
- }
826
- }
827
- } else {
828
- const prevProps =
829
- finishedWork.elementType === finishedWork.type
830
- ? current.memoizedProps
831
- : resolveDefaultProps(finishedWork.type, current.memoizedProps);
832
- const prevState = current.memoizedState;
833
- // We could update instance props and state here,
834
- // but instead we rely on them being set during last render.
835
- // TODO: revisit this when we implement resuming.
836
- if (__DEV__) {
837
- if (
838
- finishedWork.type === finishedWork.elementType &&
839
- !didWarnAboutReassigningProps
840
- ) {
841
- if (instance.props !== finishedWork.memoizedProps) {
842
- console.error(
843
- 'Expected %s props to match memoized props before ' +
844
- 'componentDidUpdate. ' +
845
- 'This might either be because of a bug in React, or because ' +
846
- 'a component reassigns its own `this.props`. ' +
847
- 'Please file an issue.',
848
- getComponentNameFromFiber(finishedWork) || 'instance',
849
- );
850
- }
851
- if (instance.state !== finishedWork.memoizedState) {
852
- console.error(
853
- 'Expected %s state to match memoized state before ' +
854
- 'componentDidUpdate. ' +
855
- 'This might either be because of a bug in React, or because ' +
856
- 'a component reassigns its own `this.state`. ' +
857
- 'Please file an issue.',
858
- getComponentNameFromFiber(finishedWork) || 'instance',
859
- );
860
- }
861
- }
862
- }
863
- if (
864
- enableProfilerTimer &&
865
- enableProfilerCommitHooks &&
866
- finishedWork.mode & ProfileMode
867
- ) {
868
- try {
869
- startLayoutEffectTimer();
870
- instance.componentDidUpdate(
871
- prevProps,
872
- prevState,
873
- instance.__reactInternalSnapshotBeforeUpdate,
874
- );
875
- } catch (error) {
876
- captureCommitPhaseError(
877
- finishedWork,
878
- finishedWork.return,
879
- error,
880
- );
881
- }
882
- recordLayoutEffectDuration(finishedWork);
883
- } else {
884
- try {
885
- instance.componentDidUpdate(
886
- prevProps,
887
- prevState,
888
- instance.__reactInternalSnapshotBeforeUpdate,
889
- );
890
- } catch (error) {
891
- captureCommitPhaseError(
892
- finishedWork,
893
- finishedWork.return,
894
- error,
895
- );
896
- }
897
- }
898
- }
1006
+ commitClassLayoutLifecycles(finishedWork, current);
1007
}
1008
}
1009
1010
if (flags & Callback) {
903
- // TODO: I think this is now always non-null by the time it reaches the
904
- // commit phase. Consider removing the type check.
905
- const updateQueue: UpdateQueue<
906
- *,
907
- > | null = (finishedWork.updateQueue: any);
908
- if (updateQueue !== null) {
909
- const instance = finishedWork.stateNode;
910
- if (__DEV__) {
911
- if (
912
- finishedWork.type === finishedWork.elementType &&
913
- !didWarnAboutReassigningProps
914
- ) {
915
- if (instance.props !== finishedWork.memoizedProps) {
916
- console.error(
917
- 'Expected %s props to match memoized props before ' +
918
- 'processing the update queue. ' +
919
- 'This might either be because of a bug in React, or because ' +
920
- 'a component reassigns its own `this.props`. ' +
921
- 'Please file an issue.',
922
- getComponentNameFromFiber(finishedWork) || 'instance',
923
- );
924
- }
925
- if (instance.state !== finishedWork.memoizedState) {
926
- console.error(
927
- 'Expected %s state to match memoized state before ' +
928
- 'processing the update queue. ' +
929
- 'This might either be because of a bug in React, or because ' +
930
- 'a component reassigns its own `this.state`. ' +
931
- 'Please file an issue.',
932
- getComponentNameFromFiber(finishedWork) || 'instance',
933
- );
934
- }
935
- }
936
- }
937
- // We could update instance props and state here,
938
- // but instead we rely on them being set during last render.
939
- // TODO: revisit this when we implement resuming.
940
- try {
941
- commitCallbacks(updateQueue, instance);
942
- } catch (error) {
943
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
944
- }
945
- }
1011
+ commitClassCallbacks(finishedWork, current);
1012
}
1013
1014
if (flags & Ref) {
1015
if (!offscreenSubtreeWasHidden) {
950
- try {
951
- commitAttachRef(finishedWork);
952
- } catch (error) {
953
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
954
- }
1016
+ safelyAttachRef(finishedWork, finishedWork.return);
1017
}
1018
}
1019
break;
@@ -995,31 +1057,18 @@ function commitLayoutEffectOnFiber(
1057
finishedWork,
1058
committedLanes,
1059
);
998
- if (flags & Update) {
999
- const instance: Instance = finishedWork.stateNode;
1000
-
1001
- // Renderers may schedule work to be done after host components are mounted
1002
- // (eg DOM renderer may schedule auto-focus for inputs and form controls).
1003
- // These effects should only be committed when components are first mounted,
1004
- // aka when there is no current/alternate.
1005
- if (current === null && finishedWork.flags & Update) {
1006
- const type = finishedWork.type;
1007
- const props = finishedWork.memoizedProps;
1008
- try {
1009
- commitMount(instance, type, props, finishedWork);
1010
- } catch (error) {
1011
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
1012
- }
1013
- }
1060
+
1061
+ // Renderers may schedule work to be done after host components are mounted
1062
+ // (eg DOM renderer may schedule auto-focus for inputs and form controls).
1063
+ // These effects should only be committed when components are first mounted,
1064
+ // aka when there is no current/alternate.
1065
+ if (current === null && flags & Update) {
1066
+ commitHostComponentMount(finishedWork, current);
1067
}
1068
1069
if (flags & Ref) {
1070
if (!offscreenSubtreeWasHidden) {
1018
- try {
1019
- commitAttachRef(finishedWork);
1020
- } catch (error) {
1021
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
1022
- }
1071
+ safelyAttachRef(finishedWork, finishedWork.return);
1072
}
1073
}
1074
break;
@@ -1030,68 +1079,10 @@ function commitLayoutEffectOnFiber(
1079
finishedWork,
1080
committedLanes,
1081
);
1033
- if (enableProfilerTimer) {
1034
- if (flags & Update) {
1035
- try {
1036
- const {onCommit, onRender} = finishedWork.memoizedProps;
1037
- const {effectDuration} = finishedWork.stateNode;
1038
-
1039
- const commitTime = getCommitTime();
1040
-
1041
- let phase = current === null ? 'mount' : 'update';
1042
- if (enableProfilerNestedUpdatePhase) {
1043
- if (isCurrentUpdateNested()) {
1044
- phase = 'nested-update';
1045
- }
1046
- }
1047
-
1048
- if (typeof onRender === 'function') {
1049
- onRender(
1050
- finishedWork.memoizedProps.id,
1051
- phase,
1052
- finishedWork.actualDuration,
1053
- finishedWork.treeBaseDuration,
1054
- finishedWork.actualStartTime,
1055
- commitTime,
1056
- );
1057
- }
1058
-
1059
- if (enableProfilerCommitHooks) {
1060
- if (typeof onCommit === 'function') {
1061
- onCommit(
1062
- finishedWork.memoizedProps.id,
1063
- phase,
1064
- effectDuration,
1065
- commitTime,
1066
- );
1067
- }
1068
-
1069
- // Schedule a passive effect for this Profiler to call onPostCommit hooks.
1070
- // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
1071
- // because the effect is also where times bubble to parent Profilers.
1072
- enqueuePendingPassiveProfilerEffect(finishedWork);
1073
-
1074
- // Propagate layout effect durations to the next nearest Profiler ancestor.
1075
- // Do not reset these values until the next render so DevTools has a chance to read them first.
1076
- let parentFiber = finishedWork.return;
1077
- outer: while (parentFiber !== null) {
1078
- switch (parentFiber.tag) {
1079
- case HostRoot:
1080
- const root = parentFiber.stateNode;
1081
- root.effectDuration += effectDuration;
1082
- break outer;
1083
- case Profiler:
1084
- const parentStateNode = parentFiber.stateNode;
1085
- parentStateNode.effectDuration += effectDuration;
1086
- break outer;
1087
- }
1088
- parentFiber = parentFiber.return;
1089
- }
1090
- }
1091
- } catch (error) {
1092
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
1093
- }
1094
- }
1082
+ // TODO: Should this fire inside an offscreen tree? Or should it wait to
1083
+ // fire when the tree becomes visible again.
1084
+ if (flags & Update) {
1085
+ commitProfilerUpdate(finishedWork, current);
1086
}
1087
break;
1088
}
@@ -1102,11 +1093,7 @@ function commitLayoutEffectOnFiber(
1093
committedLanes,
1094
);
1095
if (flags & Update) {
1105
- try {
1106
- commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1107
- } catch (error) {
1108
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
1109
- }
1096
+ commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1097
}
1098
break;
1099
}
@@ -2079,15 +2066,19 @@ function commitSuspenseHydrationCallbacks(
2066
if (prevState !== null) {
2067
const suspenseInstance = prevState.dehydrated;
2068
if (suspenseInstance !== null) {
2082
- commitHydratedSuspenseInstance(suspenseInstance);
2083
- if (enableSuspenseCallback) {
2084
- const hydrationCallbacks = finishedRoot.hydrationCallbacks;
2085
- if (hydrationCallbacks !== null) {
2086
- const onHydrated = hydrationCallbacks.onHydrated;
2087
- if (onHydrated) {
2088
- onHydrated(suspenseInstance);
2069
+ try {
2070
+ commitHydratedSuspenseInstance(suspenseInstance);
2071
+ if (enableSuspenseCallback) {
2072
+ const hydrationCallbacks = finishedRoot.hydrationCallbacks;
2073
+ if (hydrationCallbacks !== null) {
2074
+ const onHydrated = hydrationCallbacks.onHydrated;
2075
+ if (onHydrated) {
2076
+ onHydrated(suspenseInstance);
2077
+ }
2078
}
2079
}
2080
+ } catch (error) {
2081
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
2082
}
2083
}
2084
}
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+294
-303
@@ -706,6 +706,272 @@ export function commitPassiveEffectDurations(
706
}
707
}
708
709
+function commitHookLayoutEffects(finishedWork: Fiber) {
710
+ // At this point layout effects have already been destroyed (during mutation phase).
711
+ // This is done to prevent sibling component effects from interfering with each other,
712
+ // e.g. a destroy function in one component should never override a ref set
713
+ // by a create function in another component during the same commit.
714
+ if (
715
+ enableProfilerTimer &&
716
+ enableProfilerCommitHooks &&
717
+ finishedWork.mode & ProfileMode
718
+ ) {
719
+ try {
720
+ startLayoutEffectTimer();
721
+ commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
722
+ } catch (error) {
723
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
724
+ }
725
+ recordLayoutEffectDuration(finishedWork);
726
+ } else {
727
+ try {
728
+ commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
729
+ } catch (error) {
730
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
731
+ }
732
+ }
733
+}
734
+
735
+function commitClassLayoutLifecycles(
736
+ finishedWork: Fiber,
737
+ current: Fiber | null,
738
+) {
739
+ const instance = finishedWork.stateNode;
740
+ if (current === null) {
741
+ // We could update instance props and state here,
742
+ // but instead we rely on them being set during last render.
743
+ // TODO: revisit this when we implement resuming.
744
+ if (__DEV__) {
745
+ if (
746
+ finishedWork.type === finishedWork.elementType &&
747
+ !didWarnAboutReassigningProps
748
+ ) {
749
+ if (instance.props !== finishedWork.memoizedProps) {
750
+ console.error(
751
+ 'Expected %s props to match memoized props before ' +
752
+ 'componentDidMount. ' +
753
+ 'This might either be because of a bug in React, or because ' +
754
+ 'a component reassigns its own `this.props`. ' +
755
+ 'Please file an issue.',
756
+ getComponentNameFromFiber(finishedWork) || 'instance',
757
+ );
758
+ }
759
+ if (instance.state !== finishedWork.memoizedState) {
760
+ console.error(
761
+ 'Expected %s state to match memoized state before ' +
762
+ 'componentDidMount. ' +
763
+ 'This might either be because of a bug in React, or because ' +
764
+ 'a component reassigns its own `this.state`. ' +
765
+ 'Please file an issue.',
766
+ getComponentNameFromFiber(finishedWork) || 'instance',
767
+ );
768
+ }
769
+ }
770
+ }
771
+ if (
772
+ enableProfilerTimer &&
773
+ enableProfilerCommitHooks &&
774
+ finishedWork.mode & ProfileMode
775
+ ) {
776
+ try {
777
+ startLayoutEffectTimer();
778
+ instance.componentDidMount();
779
+ } catch (error) {
780
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
781
+ }
782
+ recordLayoutEffectDuration(finishedWork);
783
+ } else {
784
+ try {
785
+ instance.componentDidMount();
786
+ } catch (error) {
787
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
788
+ }
789
+ }
790
+ } else {
791
+ const prevProps =
792
+ finishedWork.elementType === finishedWork.type
793
+ ? current.memoizedProps
794
+ : resolveDefaultProps(finishedWork.type, current.memoizedProps);
795
+ const prevState = current.memoizedState;
796
+ // We could update instance props and state here,
797
+ // but instead we rely on them being set during last render.
798
+ // TODO: revisit this when we implement resuming.
799
+ if (__DEV__) {
800
+ if (
801
+ finishedWork.type === finishedWork.elementType &&
802
+ !didWarnAboutReassigningProps
803
+ ) {
804
+ if (instance.props !== finishedWork.memoizedProps) {
805
+ console.error(
806
+ 'Expected %s props to match memoized props before ' +
807
+ 'componentDidUpdate. ' +
808
+ 'This might either be because of a bug in React, or because ' +
809
+ 'a component reassigns its own `this.props`. ' +
810
+ 'Please file an issue.',
811
+ getComponentNameFromFiber(finishedWork) || 'instance',
812
+ );
813
+ }
814
+ if (instance.state !== finishedWork.memoizedState) {
815
+ console.error(
816
+ 'Expected %s state to match memoized state before ' +
817
+ 'componentDidUpdate. ' +
818
+ 'This might either be because of a bug in React, or because ' +
819
+ 'a component reassigns its own `this.state`. ' +
820
+ 'Please file an issue.',
821
+ getComponentNameFromFiber(finishedWork) || 'instance',
822
+ );
823
+ }
824
+ }
825
+ }
826
+ if (
827
+ enableProfilerTimer &&
828
+ enableProfilerCommitHooks &&
829
+ finishedWork.mode & ProfileMode
830
+ ) {
831
+ try {
832
+ startLayoutEffectTimer();
833
+ instance.componentDidUpdate(
834
+ prevProps,
835
+ prevState,
836
+ instance.__reactInternalSnapshotBeforeUpdate,
837
+ );
838
+ } catch (error) {
839
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
840
+ }
841
+ recordLayoutEffectDuration(finishedWork);
842
+ } else {
843
+ try {
844
+ instance.componentDidUpdate(
845
+ prevProps,
846
+ prevState,
847
+ instance.__reactInternalSnapshotBeforeUpdate,
848
+ );
849
+ } catch (error) {
850
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
851
+ }
852
+ }
853
+ }
854
+}
855
+
856
+function commitClassCallbacks(finishedWork: Fiber, current: Fiber | null) {
857
+ // TODO: I think this is now always non-null by the time it reaches the
858
+ // commit phase. Consider removing the type check.
859
+ const updateQueue: UpdateQueue<*> | null = (finishedWork.updateQueue: any);
860
+ if (updateQueue !== null) {
861
+ const instance = finishedWork.stateNode;
862
+ if (__DEV__) {
863
+ if (
864
+ finishedWork.type === finishedWork.elementType &&
865
+ !didWarnAboutReassigningProps
866
+ ) {
867
+ if (instance.props !== finishedWork.memoizedProps) {
868
+ console.error(
869
+ 'Expected %s props to match memoized props before ' +
870
+ 'processing the update queue. ' +
871
+ 'This might either be because of a bug in React, or because ' +
872
+ 'a component reassigns its own `this.props`. ' +
873
+ 'Please file an issue.',
874
+ getComponentNameFromFiber(finishedWork) || 'instance',
875
+ );
876
+ }
877
+ if (instance.state !== finishedWork.memoizedState) {
878
+ console.error(
879
+ 'Expected %s state to match memoized state before ' +
880
+ 'processing the update queue. ' +
881
+ 'This might either be because of a bug in React, or because ' +
882
+ 'a component reassigns its own `this.state`. ' +
883
+ 'Please file an issue.',
884
+ getComponentNameFromFiber(finishedWork) || 'instance',
885
+ );
886
+ }
887
+ }
888
+ }
889
+ // We could update instance props and state here,
890
+ // but instead we rely on them being set during last render.
891
+ // TODO: revisit this when we implement resuming.
892
+ try {
893
+ commitCallbacks(updateQueue, instance);
894
+ } catch (error) {
895
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
896
+ }
897
+ }
898
+}
899
+
900
+function commitHostComponentMount(finishedWork: Fiber, current: Fiber | null) {
901
+ const type = finishedWork.type;
902
+ const props = finishedWork.memoizedProps;
903
+ const instance: Instance = finishedWork.stateNode;
904
+ try {
905
+ commitMount(instance, type, props, finishedWork);
906
+ } catch (error) {
907
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
908
+ }
909
+}
910
+
911
+function commitProfilerUpdate(finishedWork: Fiber, current: Fiber | null) {
912
+ if (enableProfilerTimer) {
913
+ try {
914
+ const {onCommit, onRender} = finishedWork.memoizedProps;
915
+ const {effectDuration} = finishedWork.stateNode;
916
+
917
+ const commitTime = getCommitTime();
918
+
919
+ let phase = current === null ? 'mount' : 'update';
920
+ if (enableProfilerNestedUpdatePhase) {
921
+ if (isCurrentUpdateNested()) {
922
+ phase = 'nested-update';
923
+ }
924
+ }
925
+
926
+ if (typeof onRender === 'function') {
927
+ onRender(
928
+ finishedWork.memoizedProps.id,
929
+ phase,
930
+ finishedWork.actualDuration,
931
+ finishedWork.treeBaseDuration,
932
+ finishedWork.actualStartTime,
933
+ commitTime,
934
+ );
935
+ }
936
+
937
+ if (enableProfilerCommitHooks) {
938
+ if (typeof onCommit === 'function') {
939
+ onCommit(
940
+ finishedWork.memoizedProps.id,
941
+ phase,
942
+ effectDuration,
943
+ commitTime,
944
+ );
945
+ }
946
+
947
+ // Schedule a passive effect for this Profiler to call onPostCommit hooks.
948
+ // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
949
+ // because the effect is also where times bubble to parent Profilers.
950
+ enqueuePendingPassiveProfilerEffect(finishedWork);
951
+
952
+ // Propagate layout effect durations to the next nearest Profiler ancestor.
953
+ // Do not reset these values until the next render so DevTools has a chance to read them first.
954
+ let parentFiber = finishedWork.return;
955
+ outer: while (parentFiber !== null) {
956
+ switch (parentFiber.tag) {
957
+ case HostRoot:
958
+ const root = parentFiber.stateNode;
959
+ root.effectDuration += effectDuration;
960
+ break outer;
961
+ case Profiler:
962
+ const parentStateNode = parentFiber.stateNode;
963
+ parentStateNode.effectDuration += effectDuration;
964
+ break outer;
965
+ }
966
+ parentFiber = parentFiber.return;
967
+ }
968
+ }
969
+ } catch (error) {
970
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
971
+ }
972
+ }
973
+}
974
+
975
function commitLayoutEffectOnFiber(
976
finishedRoot: FiberRoot,
977
current: Fiber | null,
@@ -724,35 +990,7 @@ function commitLayoutEffectOnFiber(
990
);
991
if (flags & Update) {
992
if (!offscreenSubtreeWasHidden) {
727
- // At this point layout effects have already been destroyed (during mutation phase).
728
- // This is done to prevent sibling component effects from interfering with each other,
729
- // e.g. a destroy function in one component should never override a ref set
730
- // by a create function in another component during the same commit.
731
- if (
732
- enableProfilerTimer &&
733
- enableProfilerCommitHooks &&
734
- finishedWork.mode & ProfileMode
735
- ) {
736
- try {
737
- startLayoutEffectTimer();
738
- commitHookEffectListMount(
739
- HookLayout | HookHasEffect,
740
- finishedWork,
741
- );
742
- } catch (error) {
743
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
744
- }
745
- recordLayoutEffectDuration(finishedWork);
746
- } else {
747
- try {
748
- commitHookEffectListMount(
749
- HookLayout | HookHasEffect,
750
- finishedWork,
751
- );
752
- } catch (error) {
753
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
754
- }
755
- }
993
+ commitHookLayoutEffects(finishedWork);
994
}
995
}
996
break;
@@ -765,193 +1003,17 @@ function commitLayoutEffectOnFiber(
1003
);
1004
if (flags & Update) {
1005
if (!offscreenSubtreeWasHidden) {
768
- const instance = finishedWork.stateNode;
769
- if (current === null) {
770
- // We could update instance props and state here,
771
- // but instead we rely on them being set during last render.
772
- // TODO: revisit this when we implement resuming.
773
- if (__DEV__) {
774
- if (
775
- finishedWork.type === finishedWork.elementType &&
776
- !didWarnAboutReassigningProps
777
- ) {
778
- if (instance.props !== finishedWork.memoizedProps) {
779
- console.error(
780
- 'Expected %s props to match memoized props before ' +
781
- 'componentDidMount. ' +
782
- 'This might either be because of a bug in React, or because ' +
783
- 'a component reassigns its own `this.props`. ' +
784
- 'Please file an issue.',
785
- getComponentNameFromFiber(finishedWork) || 'instance',
786
- );
787
- }
788
- if (instance.state !== finishedWork.memoizedState) {
789
- console.error(
790
- 'Expected %s state to match memoized state before ' +
791
- 'componentDidMount. ' +
792
- 'This might either be because of a bug in React, or because ' +
793
- 'a component reassigns its own `this.state`. ' +
794
- 'Please file an issue.',
795
- getComponentNameFromFiber(finishedWork) || 'instance',
796
- );
797
- }
798
- }
799
- }
800
- if (
801
- enableProfilerTimer &&
802
- enableProfilerCommitHooks &&
803
- finishedWork.mode & ProfileMode
804
- ) {
805
- try {
806
- startLayoutEffectTimer();
807
- instance.componentDidMount();
808
- } catch (error) {
809
- captureCommitPhaseError(
810
- finishedWork,
811
- finishedWork.return,
812
- error,
813
- );
814
- }
815
- recordLayoutEffectDuration(finishedWork);
816
- } else {
817
- try {
818
- instance.componentDidMount();
819
- } catch (error) {
820
- captureCommitPhaseError(
821
- finishedWork,
822
- finishedWork.return,
823
- error,
824
- );
825
- }
826
- }
827
- } else {
828
- const prevProps =
829
- finishedWork.elementType === finishedWork.type
830
- ? current.memoizedProps
831
- : resolveDefaultProps(finishedWork.type, current.memoizedProps);
832
- const prevState = current.memoizedState;
833
- // We could update instance props and state here,
834
- // but instead we rely on them being set during last render.
835
- // TODO: revisit this when we implement resuming.
836
- if (__DEV__) {
837
- if (
838
- finishedWork.type === finishedWork.elementType &&
839
- !didWarnAboutReassigningProps
840
- ) {
841
- if (instance.props !== finishedWork.memoizedProps) {
842
- console.error(
843
- 'Expected %s props to match memoized props before ' +
844
- 'componentDidUpdate. ' +
845
- 'This might either be because of a bug in React, or because ' +
846
- 'a component reassigns its own `this.props`. ' +
847
- 'Please file an issue.',
848
- getComponentNameFromFiber(finishedWork) || 'instance',
849
- );
850
- }
851
- if (instance.state !== finishedWork.memoizedState) {
852
- console.error(
853
- 'Expected %s state to match memoized state before ' +
854
- 'componentDidUpdate. ' +
855
- 'This might either be because of a bug in React, or because ' +
856
- 'a component reassigns its own `this.state`. ' +
857
- 'Please file an issue.',
858
- getComponentNameFromFiber(finishedWork) || 'instance',
859
- );
860
- }
861
- }
862
- }
863
- if (
864
- enableProfilerTimer &&
865
- enableProfilerCommitHooks &&
866
- finishedWork.mode & ProfileMode
867
- ) {
868
- try {
869
- startLayoutEffectTimer();
870
- instance.componentDidUpdate(
871
- prevProps,
872
- prevState,
873
- instance.__reactInternalSnapshotBeforeUpdate,
874
- );
875
- } catch (error) {
876
- captureCommitPhaseError(
877
- finishedWork,
878
- finishedWork.return,
879
- error,
880
- );
881
- }
882
- recordLayoutEffectDuration(finishedWork);
883
- } else {
884
- try {
885
- instance.componentDidUpdate(
886
- prevProps,
887
- prevState,
888
- instance.__reactInternalSnapshotBeforeUpdate,
889
- );
890
- } catch (error) {
891
- captureCommitPhaseError(
892
- finishedWork,
893
- finishedWork.return,
894
- error,
895
- );
896
- }
897
- }
898
- }
1006
+ commitClassLayoutLifecycles(finishedWork, current);
1007
}
1008
}
1009
1010
if (flags & Callback) {
903
- // TODO: I think this is now always non-null by the time it reaches the
904
- // commit phase. Consider removing the type check.
905
- const updateQueue: UpdateQueue<
906
- *,
907
- > | null = (finishedWork.updateQueue: any);
908
- if (updateQueue !== null) {
909
- const instance = finishedWork.stateNode;
910
- if (__DEV__) {
911
- if (
912
- finishedWork.type === finishedWork.elementType &&
913
- !didWarnAboutReassigningProps
914
- ) {
915
- if (instance.props !== finishedWork.memoizedProps) {
916
- console.error(
917
- 'Expected %s props to match memoized props before ' +
918
- 'processing the update queue. ' +
919
- 'This might either be because of a bug in React, or because ' +
920
- 'a component reassigns its own `this.props`. ' +
921
- 'Please file an issue.',
922
- getComponentNameFromFiber(finishedWork) || 'instance',
923
- );
924
- }
925
- if (instance.state !== finishedWork.memoizedState) {
926
- console.error(
927
- 'Expected %s state to match memoized state before ' +
928
- 'processing the update queue. ' +
929
- 'This might either be because of a bug in React, or because ' +
930
- 'a component reassigns its own `this.state`. ' +
931
- 'Please file an issue.',
932
- getComponentNameFromFiber(finishedWork) || 'instance',
933
- );
934
- }
935
- }
936
- }
937
- // We could update instance props and state here,
938
- // but instead we rely on them being set during last render.
939
- // TODO: revisit this when we implement resuming.
940
- try {
941
- commitCallbacks(updateQueue, instance);
942
- } catch (error) {
943
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
944
- }
945
- }
1011
+ commitClassCallbacks(finishedWork, current);
1012
}
1013
1014
if (flags & Ref) {
1015
if (!offscreenSubtreeWasHidden) {
950
- try {
951
- commitAttachRef(finishedWork);
952
- } catch (error) {
953
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
954
- }
1016
+ safelyAttachRef(finishedWork, finishedWork.return);
1017
}
1018
}
1019
break;
@@ -995,31 +1057,18 @@ function commitLayoutEffectOnFiber(
1057
finishedWork,
1058
committedLanes,
1059
);
998
- if (flags & Update) {
999
- const instance: Instance = finishedWork.stateNode;
1000
-
1001
- // Renderers may schedule work to be done after host components are mounted
1002
- // (eg DOM renderer may schedule auto-focus for inputs and form controls).
1003
- // These effects should only be committed when components are first mounted,
1004
- // aka when there is no current/alternate.
1005
- if (current === null && finishedWork.flags & Update) {
1006
- const type = finishedWork.type;
1007
- const props = finishedWork.memoizedProps;
1008
- try {
1009
- commitMount(instance, type, props, finishedWork);
1010
- } catch (error) {
1011
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
1012
- }
1013
- }
1060
+
1061
+ // Renderers may schedule work to be done after host components are mounted
1062
+ // (eg DOM renderer may schedule auto-focus for inputs and form controls).
1063
+ // These effects should only be committed when components are first mounted,
1064
+ // aka when there is no current/alternate.
1065
+ if (current === null && flags & Update) {
1066
+ commitHostComponentMount(finishedWork, current);
1067
}
1068
1069
if (flags & Ref) {
1070
if (!offscreenSubtreeWasHidden) {
1018
- try {
1019
- commitAttachRef(finishedWork);
1020
- } catch (error) {
1021
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
1022
- }
1071
+ safelyAttachRef(finishedWork, finishedWork.return);
1072
}
1073
}
1074
break;
@@ -1030,68 +1079,10 @@ function commitLayoutEffectOnFiber(
1079
finishedWork,
1080
committedLanes,
1081
);
1033
- if (enableProfilerTimer) {
1034
- if (flags & Update) {
1035
- try {
1036
- const {onCommit, onRender} = finishedWork.memoizedProps;
1037
- const {effectDuration} = finishedWork.stateNode;
1038
-
1039
- const commitTime = getCommitTime();
1040
-
1041
- let phase = current === null ? 'mount' : 'update';
1042
- if (enableProfilerNestedUpdatePhase) {
1043
- if (isCurrentUpdateNested()) {
1044
- phase = 'nested-update';
1045
- }
1046
- }
1047
-
1048
- if (typeof onRender === 'function') {
1049
- onRender(
1050
- finishedWork.memoizedProps.id,
1051
- phase,
1052
- finishedWork.actualDuration,
1053
- finishedWork.treeBaseDuration,
1054
- finishedWork.actualStartTime,
1055
- commitTime,
1056
- );
1057
- }
1058
-
1059
- if (enableProfilerCommitHooks) {
1060
- if (typeof onCommit === 'function') {
1061
- onCommit(
1062
- finishedWork.memoizedProps.id,
1063
- phase,
1064
- effectDuration,
1065
- commitTime,
1066
- );
1067
- }
1068
-
1069
- // Schedule a passive effect for this Profiler to call onPostCommit hooks.
1070
- // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
1071
- // because the effect is also where times bubble to parent Profilers.
1072
- enqueuePendingPassiveProfilerEffect(finishedWork);
1073
-
1074
- // Propagate layout effect durations to the next nearest Profiler ancestor.
1075
- // Do not reset these values until the next render so DevTools has a chance to read them first.
1076
- let parentFiber = finishedWork.return;
1077
- outer: while (parentFiber !== null) {
1078
- switch (parentFiber.tag) {
1079
- case HostRoot:
1080
- const root = parentFiber.stateNode;
1081
- root.effectDuration += effectDuration;
1082
- break outer;
1083
- case Profiler:
1084
- const parentStateNode = parentFiber.stateNode;
1085
- parentStateNode.effectDuration += effectDuration;
1086
- break outer;
1087
- }
1088
- parentFiber = parentFiber.return;
1089
- }
1090
- }
1091
- } catch (error) {
1092
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
1093
- }
1094
- }
1082
+ // TODO: Should this fire inside an offscreen tree? Or should it wait to
1083
+ // fire when the tree becomes visible again.
1084
+ if (flags & Update) {
1085
+ commitProfilerUpdate(finishedWork, current);
1086
}
1087
break;
1088
}
@@ -1102,11 +1093,7 @@ function commitLayoutEffectOnFiber(
1093
committedLanes,
1094
);
1095
if (flags & Update) {
1105
- try {
1106
- commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1107
- } catch (error) {
1108
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
1109
- }
1096
+ commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1097
}
1098
break;
1099
}
@@ -2079,15 +2066,19 @@ function commitSuspenseHydrationCallbacks(
2066
if (prevState !== null) {
2067
const suspenseInstance = prevState.dehydrated;
2068
if (suspenseInstance !== null) {
2082
- commitHydratedSuspenseInstance(suspenseInstance);
2083
- if (enableSuspenseCallback) {
2084
- const hydrationCallbacks = finishedRoot.hydrationCallbacks;
2085
- if (hydrationCallbacks !== null) {
2086
- const onHydrated = hydrationCallbacks.onHydrated;
2087
- if (onHydrated) {
2088
- onHydrated(suspenseInstance);
2069
+ try {
2070
+ commitHydratedSuspenseInstance(suspenseInstance);
2071
+ if (enableSuspenseCallback) {
2072
+ const hydrationCallbacks = finishedRoot.hydrationCallbacks;
2073
+ if (hydrationCallbacks !== null) {
2074
+ const onHydrated = hydrationCallbacks.onHydrated;
2075
+ if (onHydrated) {
2076
+ onHydrated(suspenseInstance);
2077
+ }
2078
}
2079
}
2080
+ } catch (error) {
2081
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
2082
}
2083
}
2084
}