@samitouri / QOS-React / commits / c99c5f1df6

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,