@samitouri / QOS-React-2 / commits / 679eea3282

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 }