Move ad hoc flag checks into main switch statement
We should always refine the type of fiber before checking the effect flag, because the fiber tag is more specific. Now we have a single switch statement for all mutation effects.
Andrew Clark committed
Apr 7, 2022 at 20:05 UTC
c99c5f1df6e3c274d34bd2e5f0b27ac4af481f94
2 files changed
+240
-198
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+120
-99
@@ -1899,13 +1899,6 @@ export function isSuspenseBoundaryBeingHidden(
1899
return false;
1900
}
1901
1902
-function commitResetTextContent(current: Fiber) {
1903
- if (!supportsMutation) {
1904
- return;
1905
- }
1906
- resetTextContent(current.stateNode);
1907
-}
1908
-
1902
export function commitMutationEffects(
1903
root: FiberRoot,
1904
firstChild: Fiber,
@@ -1977,101 +1970,19 @@ function commitMutationEffectsOnFiber(
1970
root: FiberRoot,
1971
lanes: Lanes,
1972
) {
1980
- // TODO: The factoring of this phase could probably be improved. Consider
1981
- // switching on the type of work before checking the flags. That's what
1982
- // we do in all the other phases. I think this one is only different
1983
- // because of the shared reconciliation logic below.
1973
const current = finishedWork.alternate;
1974
const flags = finishedWork.flags;
1975
1987
- if (flags & ContentReset) {
1988
- commitResetTextContent(finishedWork);
1989
- }
1990
-
1991
- if (flags & Ref) {
1992
- if (current !== null) {
1993
- commitDetachRef(current);
1994
- }
1995
- if (enableScopeAPI) {
1996
- // TODO: This is a temporary solution that allowed us to transition away
1997
- // from React Flare on www.
1998
- if (finishedWork.tag === ScopeComponent) {
1999
- commitAttachRef(finishedWork);
2000
- }
2001
- }
2002
- }
2003
-
2004
- if (flags & Visibility) {
2005
- switch (finishedWork.tag) {
2006
- case SuspenseComponent: {
2007
- const newState: OffscreenState | null = finishedWork.memoizedState;
2008
- const isHidden = newState !== null;
2009
- if (isHidden) {
2010
- const wasHidden = current !== null && current.memoizedState !== null;
2011
- if (!wasHidden) {
2012
- // TODO: Move to passive phase
2013
- markCommitTimeOfFallback();
2014
- }
2015
- }
2016
- break;
2017
- }
2018
- case OffscreenComponent: {
2019
- const newState: OffscreenState | null = finishedWork.memoizedState;
2020
- const isHidden = newState !== null;
2021
- const wasHidden = current !== null && current.memoizedState !== null;
2022
- const offscreenBoundary: Fiber = finishedWork;
2023
-
2024
- if (supportsMutation) {
2025
- // TODO: This needs to run whenever there's an insertion or update
2026
- // inside a hidden Offscreen tree.
2027
- hideOrUnhideAllChildren(offscreenBoundary, isHidden);
2028
- }
2029
-
2030
- if (enableSuspenseLayoutEffectSemantics) {
2031
- if (isHidden) {
2032
- if (!wasHidden) {
2033
- if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) {
2034
- nextEffect = offscreenBoundary;
2035
- let offscreenChild = offscreenBoundary.child;
2036
- while (offscreenChild !== null) {
2037
- nextEffect = offscreenChild;
2038
- disappearLayoutEffects_begin(offscreenChild);
2039
- offscreenChild = offscreenChild.sibling;
2040
- }
2041
- }
2042
- }
2043
- } else {
2044
- if (wasHidden) {
2045
- // TODO: Move re-appear call here for symmetry?
2046
- }
2047
- }
2048
- break;
2049
- }
2050
- }
2051
- }
2052
- }
2053
-
2054
- // These are related to reconciliation so they affect every fiber type; that's
2055
- // why they aren't in the main switch statement below.
2056
- if (flags & Placement) {
2057
- commitPlacement(finishedWork);
2058
- // Clear the "placement" from effect tag so that we know that this is
2059
- // inserted, before any life-cycles like componentDidMount gets called.
2060
- // TODO: findDOMNode doesn't rely on this any more but isMounted does
2061
- // and isMounted is deprecated anyway so we should be able to kill this.
2062
- finishedWork.flags &= ~Placement;
2063
- }
2064
- if (flags & Hydrating) {
2065
- finishedWork.flags &= ~Hydrating;
2066
- }
2067
-
2068
- // All logic in these branches should be wrapped in a flag check.
2069
- // TODO: Move the ad-hoc flag checks above into the main switch statement.
1976
+ // The effect flag should be checked *after* we refine the type of fiber,
1977
+ // because the fiber tag is more specific. An exception is any flag related
1978
+ // to reconcilation, because those can be set on all fiber types.
1979
switch (finishedWork.tag) {
1980
case FunctionComponent:
1981
case ForwardRef:
1982
case MemoComponent:
1983
case SimpleMemoComponent: {
1984
+ commitReconciliationEffects(finishedWork);
1985
+
1986
if (flags & Update) {
1987
commitHookEffectListUnmount(
1988
HookInsertion | HookHasEffect,
@@ -2109,9 +2020,31 @@ function commitMutationEffectsOnFiber(
2020
}
2021
return;
2022
}
2023
+ case ClassComponent: {
2024
+ commitReconciliationEffects(finishedWork);
2025
+
2026
+ if (flags & Ref) {
2027
+ if (current !== null) {
2028
+ commitDetachRef(current);
2029
+ }
2030
+ }
2031
+ return;
2032
+ }
2033
case HostComponent: {
2113
- if (flags & Update) {
2114
- if (supportsMutation) {
2034
+ commitReconciliationEffects(finishedWork);
2035
+
2036
+ if (flags & Ref) {
2037
+ if (current !== null) {
2038
+ commitDetachRef(current);
2039
+ }
2040
+ }
2041
+ if (supportsMutation) {
2042
+ if (flags & ContentReset) {
2043
+ const instance: Instance = finishedWork.stateNode;
2044
+ resetTextContent(instance);
2045
+ }
2046
+
2047
+ if (flags & Update) {
2048
const instance: Instance = finishedWork.stateNode;
2049
if (instance != null) {
2050
// Commit the work prepared earlier.
@@ -2141,6 +2074,8 @@ function commitMutationEffectsOnFiber(
2074
return;
2075
}
2076
case HostText: {
2077
+ commitReconciliationEffects(finishedWork);
2078
+
2079
if (flags & Update) {
2080
if (supportsMutation) {
2081
if (finishedWork.stateNode === null) {
@@ -2163,6 +2098,8 @@ function commitMutationEffectsOnFiber(
2098
return;
2099
}
2100
case HostRoot: {
2101
+ commitReconciliationEffects(finishedWork);
2102
+
2103
if (flags & Update) {
2104
if (supportsMutation && supportsHydration) {
2105
if (current !== null) {
@@ -2181,6 +2118,8 @@ function commitMutationEffectsOnFiber(
2118
return;
2119
}
2120
case HostPortal: {
2121
+ commitReconciliationEffects(finishedWork);
2122
+
2123
if (flags & Update) {
2124
if (supportsPersistence) {
2125
const portal = finishedWork.stateNode;
@@ -2192,27 +2131,110 @@ function commitMutationEffectsOnFiber(
2131
return;
2132
}
2133
case SuspenseComponent: {
2134
+ commitReconciliationEffects(finishedWork);
2135
+
2136
+ if (flags & Visibility) {
2137
+ const newState: OffscreenState | null = finishedWork.memoizedState;
2138
+ const isHidden = newState !== null;
2139
+ if (isHidden) {
2140
+ const wasHidden = current !== null && current.memoizedState !== null;
2141
+ if (!wasHidden) {
2142
+ // TODO: Move to passive phase
2143
+ markCommitTimeOfFallback();
2144
+ }
2145
+ }
2146
+ }
2147
if (flags & Update) {
2148
commitSuspenseCallback(finishedWork);
2149
attachSuspenseRetryListeners(finishedWork);
2150
}
2151
return;
2152
}
2153
+ case OffscreenComponent: {
2154
+ commitReconciliationEffects(finishedWork);
2155
+
2156
+ if (flags & Visibility) {
2157
+ const newState: OffscreenState | null = finishedWork.memoizedState;
2158
+ const isHidden = newState !== null;
2159
+ const wasHidden = current !== null && current.memoizedState !== null;
2160
+ const offscreenBoundary: Fiber = finishedWork;
2161
+
2162
+ if (supportsMutation) {
2163
+ // TODO: This needs to run whenever there's an insertion or update
2164
+ // inside a hidden Offscreen tree.
2165
+ hideOrUnhideAllChildren(offscreenBoundary, isHidden);
2166
+ }
2167
+
2168
+ if (enableSuspenseLayoutEffectSemantics) {
2169
+ if (isHidden) {
2170
+ if (!wasHidden) {
2171
+ if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) {
2172
+ nextEffect = offscreenBoundary;
2173
+ let offscreenChild = offscreenBoundary.child;
2174
+ while (offscreenChild !== null) {
2175
+ nextEffect = offscreenChild;
2176
+ disappearLayoutEffects_begin(offscreenChild);
2177
+ offscreenChild = offscreenChild.sibling;
2178
+ }
2179
+ }
2180
+ }
2181
+ } else {
2182
+ if (wasHidden) {
2183
+ // TODO: Move re-appear call here for symmetry?
2184
+ }
2185
+ }
2186
+ }
2187
+ }
2188
+ return;
2189
+ }
2190
case SuspenseListComponent: {
2191
+ commitReconciliationEffects(finishedWork);
2192
+
2193
if (flags & Update) {
2194
attachSuspenseRetryListeners(finishedWork);
2195
}
2196
return;
2197
}
2198
case ScopeComponent: {
2208
- if (flags & Update) {
2209
- if (enableScopeAPI) {
2199
+ if (enableScopeAPI) {
2200
+ commitReconciliationEffects(finishedWork);
2201
+
2202
+ // TODO: This is a temporary solution that allowed us to transition away
2203
+ // from React Flare on www.
2204
+ if (flags & Ref) {
2205
+ if (current !== null) {
2206
+ commitDetachRef(current);
2207
+ }
2208
+ commitAttachRef(finishedWork);
2209
+ }
2210
+ if (flags & Update) {
2211
const scopeInstance = finishedWork.stateNode;
2212
prepareScopeUpdate(scopeInstance, finishedWork);
2213
}
2214
}
2215
return;
2216
}
2217
+ default: {
2218
+ commitReconciliationEffects(finishedWork);
2219
+ }
2220
+ }
2221
+}
2222
+
2223
+function commitReconciliationEffects(finishedWork: Fiber) {
2224
+ // Placement effects (insertions, reorders) can be scheduled on any fiber
2225
+ // type. They needs to happen after the children effects have fired, but
2226
+ // before the effects on this fiber have fired.
2227
+ const flags = finishedWork.flags;
2228
+ if (flags & Placement) {
2229
+ commitPlacement(finishedWork);
2230
+ // Clear the "placement" from effect tag so that we know that this is
2231
+ // inserted, before any life-cycles like componentDidMount gets called.
2232
+ // TODO: findDOMNode doesn't rely on this any more but isMounted does
2233
+ // and isMounted is deprecated anyway so we should be able to kill this.
2234
+ finishedWork.flags &= ~Placement;
2235
+ }
2236
+ if (flags & Hydrating) {
2237
+ finishedWork.flags &= ~Hydrating;
2238
}
2239
}
2240
@@ -3017,7 +3039,6 @@ function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
3039
}
3040
3041
export {
3020
- commitResetTextContent,
3042
commitPlacement,
3043
commitDeletion,
3044
commitAttachRef,
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+120
-99
@@ -1899,13 +1899,6 @@ export function isSuspenseBoundaryBeingHidden(
1899
return false;
1900
}
1901
1902
-function commitResetTextContent(current: Fiber) {
1903
- if (!supportsMutation) {
1904
- return;
1905
- }
1906
- resetTextContent(current.stateNode);
1907
-}
1908
-
1902
export function commitMutationEffects(
1903
root: FiberRoot,
1904
firstChild: Fiber,
@@ -1977,101 +1970,19 @@ function commitMutationEffectsOnFiber(
1970
root: FiberRoot,
1971
lanes: Lanes,
1972
) {
1980
- // TODO: The factoring of this phase could probably be improved. Consider
1981
- // switching on the type of work before checking the flags. That's what
1982
- // we do in all the other phases. I think this one is only different
1983
- // because of the shared reconciliation logic below.
1973
const current = finishedWork.alternate;
1974
const flags = finishedWork.flags;
1975
1987
- if (flags & ContentReset) {
1988
- commitResetTextContent(finishedWork);
1989
- }
1990
-
1991
- if (flags & Ref) {
1992
- if (current !== null) {
1993
- commitDetachRef(current);
1994
- }
1995
- if (enableScopeAPI) {
1996
- // TODO: This is a temporary solution that allowed us to transition away
1997
- // from React Flare on www.
1998
- if (finishedWork.tag === ScopeComponent) {
1999
- commitAttachRef(finishedWork);
2000
- }
2001
- }
2002
- }
2003
-
2004
- if (flags & Visibility) {
2005
- switch (finishedWork.tag) {
2006
- case SuspenseComponent: {
2007
- const newState: OffscreenState | null = finishedWork.memoizedState;
2008
- const isHidden = newState !== null;
2009
- if (isHidden) {
2010
- const wasHidden = current !== null && current.memoizedState !== null;
2011
- if (!wasHidden) {
2012
- // TODO: Move to passive phase
2013
- markCommitTimeOfFallback();
2014
- }
2015
- }
2016
- break;
2017
- }
2018
- case OffscreenComponent: {
2019
- const newState: OffscreenState | null = finishedWork.memoizedState;
2020
- const isHidden = newState !== null;
2021
- const wasHidden = current !== null && current.memoizedState !== null;
2022
- const offscreenBoundary: Fiber = finishedWork;
2023
-
2024
- if (supportsMutation) {
2025
- // TODO: This needs to run whenever there's an insertion or update
2026
- // inside a hidden Offscreen tree.
2027
- hideOrUnhideAllChildren(offscreenBoundary, isHidden);
2028
- }
2029
-
2030
- if (enableSuspenseLayoutEffectSemantics) {
2031
- if (isHidden) {
2032
- if (!wasHidden) {
2033
- if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) {
2034
- nextEffect = offscreenBoundary;
2035
- let offscreenChild = offscreenBoundary.child;
2036
- while (offscreenChild !== null) {
2037
- nextEffect = offscreenChild;
2038
- disappearLayoutEffects_begin(offscreenChild);
2039
- offscreenChild = offscreenChild.sibling;
2040
- }
2041
- }
2042
- }
2043
- } else {
2044
- if (wasHidden) {
2045
- // TODO: Move re-appear call here for symmetry?
2046
- }
2047
- }
2048
- break;
2049
- }
2050
- }
2051
- }
2052
- }
2053
-
2054
- // These are related to reconciliation so they affect every fiber type; that's
2055
- // why they aren't in the main switch statement below.
2056
- if (flags & Placement) {
2057
- commitPlacement(finishedWork);
2058
- // Clear the "placement" from effect tag so that we know that this is
2059
- // inserted, before any life-cycles like componentDidMount gets called.
2060
- // TODO: findDOMNode doesn't rely on this any more but isMounted does
2061
- // and isMounted is deprecated anyway so we should be able to kill this.
2062
- finishedWork.flags &= ~Placement;
2063
- }
2064
- if (flags & Hydrating) {
2065
- finishedWork.flags &= ~Hydrating;
2066
- }
2067
-
2068
- // All logic in these branches should be wrapped in a flag check.
2069
- // TODO: Move the ad-hoc flag checks above into the main switch statement.
1976
+ // The effect flag should be checked *after* we refine the type of fiber,
1977
+ // because the fiber tag is more specific. An exception is any flag related
1978
+ // to reconcilation, because those can be set on all fiber types.
1979
switch (finishedWork.tag) {
1980
case FunctionComponent:
1981
case ForwardRef:
1982
case MemoComponent:
1983
case SimpleMemoComponent: {
1984
+ commitReconciliationEffects(finishedWork);
1985
+
1986
if (flags & Update) {
1987
commitHookEffectListUnmount(
1988
HookInsertion | HookHasEffect,
@@ -2109,9 +2020,31 @@ function commitMutationEffectsOnFiber(
2020
}
2021
return;
2022
}
2023
+ case ClassComponent: {
2024
+ commitReconciliationEffects(finishedWork);
2025
+
2026
+ if (flags & Ref) {
2027
+ if (current !== null) {
2028
+ commitDetachRef(current);
2029
+ }
2030
+ }
2031
+ return;
2032
+ }
2033
case HostComponent: {
2113
- if (flags & Update) {
2114
- if (supportsMutation) {
2034
+ commitReconciliationEffects(finishedWork);
2035
+
2036
+ if (flags & Ref) {
2037
+ if (current !== null) {
2038
+ commitDetachRef(current);
2039
+ }
2040
+ }
2041
+ if (supportsMutation) {
2042
+ if (flags & ContentReset) {
2043
+ const instance: Instance = finishedWork.stateNode;
2044
+ resetTextContent(instance);
2045
+ }
2046
+
2047
+ if (flags & Update) {
2048
const instance: Instance = finishedWork.stateNode;
2049
if (instance != null) {
2050
// Commit the work prepared earlier.
@@ -2141,6 +2074,8 @@ function commitMutationEffectsOnFiber(
2074
return;
2075
}
2076
case HostText: {
2077
+ commitReconciliationEffects(finishedWork);
2078
+
2079
if (flags & Update) {
2080
if (supportsMutation) {
2081
if (finishedWork.stateNode === null) {
@@ -2163,6 +2098,8 @@ function commitMutationEffectsOnFiber(
2098
return;
2099
}
2100
case HostRoot: {
2101
+ commitReconciliationEffects(finishedWork);
2102
+
2103
if (flags & Update) {
2104
if (supportsMutation && supportsHydration) {
2105
if (current !== null) {
@@ -2181,6 +2118,8 @@ function commitMutationEffectsOnFiber(
2118
return;
2119
}
2120
case HostPortal: {
2121
+ commitReconciliationEffects(finishedWork);
2122
+
2123
if (flags & Update) {
2124
if (supportsPersistence) {
2125
const portal = finishedWork.stateNode;
@@ -2192,27 +2131,110 @@ function commitMutationEffectsOnFiber(
2131
return;
2132
}
2133
case SuspenseComponent: {
2134
+ commitReconciliationEffects(finishedWork);
2135
+
2136
+ if (flags & Visibility) {
2137
+ const newState: OffscreenState | null = finishedWork.memoizedState;
2138
+ const isHidden = newState !== null;
2139
+ if (isHidden) {
2140
+ const wasHidden = current !== null && current.memoizedState !== null;
2141
+ if (!wasHidden) {
2142
+ // TODO: Move to passive phase
2143
+ markCommitTimeOfFallback();
2144
+ }
2145
+ }
2146
+ }
2147
if (flags & Update) {
2148
commitSuspenseCallback(finishedWork);
2149
attachSuspenseRetryListeners(finishedWork);
2150
}
2151
return;
2152
}
2153
+ case OffscreenComponent: {
2154
+ commitReconciliationEffects(finishedWork);
2155
+
2156
+ if (flags & Visibility) {
2157
+ const newState: OffscreenState | null = finishedWork.memoizedState;
2158
+ const isHidden = newState !== null;
2159
+ const wasHidden = current !== null && current.memoizedState !== null;
2160
+ const offscreenBoundary: Fiber = finishedWork;
2161
+
2162
+ if (supportsMutation) {
2163
+ // TODO: This needs to run whenever there's an insertion or update
2164
+ // inside a hidden Offscreen tree.
2165
+ hideOrUnhideAllChildren(offscreenBoundary, isHidden);
2166
+ }
2167
+
2168
+ if (enableSuspenseLayoutEffectSemantics) {
2169
+ if (isHidden) {
2170
+ if (!wasHidden) {
2171
+ if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) {
2172
+ nextEffect = offscreenBoundary;
2173
+ let offscreenChild = offscreenBoundary.child;
2174
+ while (offscreenChild !== null) {
2175
+ nextEffect = offscreenChild;
2176
+ disappearLayoutEffects_begin(offscreenChild);
2177
+ offscreenChild = offscreenChild.sibling;
2178
+ }
2179
+ }
2180
+ }
2181
+ } else {
2182
+ if (wasHidden) {
2183
+ // TODO: Move re-appear call here for symmetry?
2184
+ }
2185
+ }
2186
+ }
2187
+ }
2188
+ return;
2189
+ }
2190
case SuspenseListComponent: {
2191
+ commitReconciliationEffects(finishedWork);
2192
+
2193
if (flags & Update) {
2194
attachSuspenseRetryListeners(finishedWork);
2195
}
2196
return;
2197
}
2198
case ScopeComponent: {
2208
- if (flags & Update) {
2209
- if (enableScopeAPI) {
2199
+ if (enableScopeAPI) {
2200
+ commitReconciliationEffects(finishedWork);
2201
+
2202
+ // TODO: This is a temporary solution that allowed us to transition away
2203
+ // from React Flare on www.
2204
+ if (flags & Ref) {
2205
+ if (current !== null) {
2206
+ commitDetachRef(current);
2207
+ }
2208
+ commitAttachRef(finishedWork);
2209
+ }
2210
+ if (flags & Update) {
2211
const scopeInstance = finishedWork.stateNode;
2212
prepareScopeUpdate(scopeInstance, finishedWork);
2213
}
2214
}
2215
return;
2216
}
2217
+ default: {
2218
+ commitReconciliationEffects(finishedWork);
2219
+ }
2220
+ }
2221
+}
2222
+
2223
+function commitReconciliationEffects(finishedWork: Fiber) {
2224
+ // Placement effects (insertions, reorders) can be scheduled on any fiber
2225
+ // type. They needs to happen after the children effects have fired, but
2226
+ // before the effects on this fiber have fired.
2227
+ const flags = finishedWork.flags;
2228
+ if (flags & Placement) {
2229
+ commitPlacement(finishedWork);
2230
+ // Clear the "placement" from effect tag so that we know that this is
2231
+ // inserted, before any life-cycles like componentDidMount gets called.
2232
+ // TODO: findDOMNode doesn't rely on this any more but isMounted does
2233
+ // and isMounted is deprecated anyway so we should be able to kill this.
2234
+ finishedWork.flags &= ~Placement;
2235
+ }
2236
+ if (flags & Hydrating) {
2237
+ finishedWork.flags &= ~Hydrating;
2238
}
2239
}
2240
@@ -3017,7 +3039,6 @@ function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
3039
}
3040
3041
export {
3020
- commitResetTextContent,
3042
commitPlacement,
3043
commitDeletion,
3044
commitAttachRef,