@samitouri / QOS-React-2 / commits / 95feb0e701

Convert mutation phase to depth-first traversal (#20596)

Andrew Clark committed Jan 15, 2021 at 14:24 UTC 95feb0e701a5ae20996e8cc6c4acd0f504d5985a
5 files changed +180 -170
packages/react-reconciler/src/ReactChildFiber.new.js
+2 -11
@@ -282,22 +282,13 @@ function ChildReconciler(shouldTrackSideEffects) {
282 childToDelete.nextEffect = null;
283 childToDelete.flags = (childToDelete.flags & StaticMask) | Deletion;
284
285 - let deletions = returnFiber.deletions;
285 + const deletions = returnFiber.deletions;
286 if (deletions === null) {
287 - deletions = returnFiber.deletions = [childToDelete];
287 + returnFiber.deletions = [childToDelete];
288 returnFiber.flags |= ChildDeletion;
289 } else {
290 deletions.push(childToDelete);
291 }
292 - // Stash a reference to the return fiber's deletion array on each of the
293 - // deleted children. This is really weird, but it's a temporary workaround
294 - // while we're still using the effect list to traverse effect fibers. A
295 - // better workaround would be to follow the `.return` pointer in the commit
296 - // phase, but unfortunately we can't assume that `.return` points to the
297 - // correct fiber, even in the commit phase, because `findDOMNode` might
298 - // mutate it.
299 - // TODO: Remove this line.
300 - childToDelete.deletions = deletions;
292 }
293
294 function deleteRemainingChildren(
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+4 -6
@@ -2203,14 +2203,13 @@ function updateSuspensePrimaryChildren(
2203 currentFallbackChildFragment.flags =
2204 (currentFallbackChildFragment.flags & StaticMask) | Deletion;
2205 workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChildFragment;
2206 - let deletions = workInProgress.deletions;
2206 + const deletions = workInProgress.deletions;
2207 if (deletions === null) {
2208 - deletions = workInProgress.deletions = [currentFallbackChildFragment];
2208 + workInProgress.deletions = [currentFallbackChildFragment];
2209 workInProgress.flags |= ChildDeletion;
2210 } else {
2211 deletions.push(currentFallbackChildFragment);
2212 }
2213 - currentFallbackChildFragment.deletions = deletions;
2213 }
2214
2215 workInProgress.child = primaryChildFragment;
@@ -3194,14 +3193,13 @@ function remountFiber(
3193 current.nextEffect = null;
3194 current.flags = (current.flags & StaticMask) | Deletion;
3195
3197 - let deletions = returnFiber.deletions;
3196 + const deletions = returnFiber.deletions;
3197 if (deletions === null) {
3199 - deletions = returnFiber.deletions = [current];
3198 + returnFiber.deletions = [current];
3199 returnFiber.flags |= ChildDeletion;
3200 } else {
3201 deletions.push(current);
3202 }
3204 - current.deletions = deletions;
3203
3204 newWorkInProgress.flags |= Placement;
3205
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+170
@@ -66,12 +66,16 @@ import {
66 NoFlags,
67 ContentReset,
68 Placement,
69 + PlacementAndUpdate,
70 ChildDeletion,
71 Snapshot,
72 Update,
73 Callback,
74 Ref,
75 + Hydrating,
76 + HydratingAndUpdate,
77 Passive,
78 + MutationMask,
79 PassiveMask,
80 LayoutMask,
81 PassiveUnmountPendingDev,
@@ -1841,6 +1845,172 @@ function commitResetTextContent(current: Fiber) {
1845 resetTextContent(current.stateNode);
1846 }
1847
1848 +export function commitMutationEffects(
1849 + root: FiberRoot,
1850 + renderPriorityLevel: ReactPriorityLevel,
1851 + firstChild: Fiber,
1852 +) {
1853 + nextEffect = firstChild;
1854 + commitMutationEffects_begin(root, renderPriorityLevel);
1855 +}
1856 +
1857 +function commitMutationEffects_begin(
1858 + root: FiberRoot,
1859 + renderPriorityLevel: ReactPriorityLevel,
1860 +) {
1861 + while (nextEffect !== null) {
1862 + const fiber = nextEffect;
1863 +
1864 + // TODO: Should wrap this in flags check, too, as optimization
1865 + const deletions = fiber.deletions;
1866 + if (deletions !== null) {
1867 + for (let i = 0; i < deletions.length; i++) {
1868 + const childToDelete = deletions[i];
1869 + if (__DEV__) {
1870 + invokeGuardedCallback(
1871 + null,
1872 + commitDeletion,
1873 + null,
1874 + root,
1875 + childToDelete,
1876 + renderPriorityLevel,
1877 + );
1878 + if (hasCaughtError()) {
1879 + const error = clearCaughtError();
1880 + captureCommitPhaseError(childToDelete, error);
1881 + }
1882 + } else {
1883 + try {
1884 + commitDeletion(root, childToDelete, renderPriorityLevel);
1885 + } catch (error) {
1886 + captureCommitPhaseError(childToDelete, error);
1887 + }
1888 + }
1889 + }
1890 + }
1891 +
1892 + const child = fiber.child;
1893 + if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
1894 + ensureCorrectReturnPointer(child, fiber);
1895 + nextEffect = child;
1896 + } else {
1897 + commitMutationEffects_complete(root, renderPriorityLevel);
1898 + }
1899 + }
1900 +}
1901 +
1902 +function commitMutationEffects_complete(
1903 + root: FiberRoot,
1904 + renderPriorityLevel: ReactPriorityLevel,
1905 +) {
1906 + while (nextEffect !== null) {
1907 + const fiber = nextEffect;
1908 + if (__DEV__) {
1909 + setCurrentDebugFiberInDEV(fiber);
1910 + invokeGuardedCallback(
1911 + null,
1912 + commitMutationEffectsOnFiber,
1913 + null,
1914 + fiber,
1915 + root,
1916 + renderPriorityLevel,
1917 + );
1918 + if (hasCaughtError()) {
1919 + const error = clearCaughtError();
1920 + captureCommitPhaseError(fiber, error);
1921 + }
1922 + resetCurrentDebugFiberInDEV();
1923 + } else {
1924 + try {
1925 + commitMutationEffectsOnFiber(fiber, root, renderPriorityLevel);
1926 + } catch (error) {
1927 + captureCommitPhaseError(fiber, error);
1928 + }
1929 + }
1930 +
1931 + const sibling = fiber.sibling;
1932 + if (sibling !== null) {
1933 + ensureCorrectReturnPointer(sibling, fiber.return);
1934 + nextEffect = sibling;
1935 + return;
1936 + }
1937 +
1938 + nextEffect = fiber.return;
1939 + }
1940 +}
1941 +
1942 +function commitMutationEffectsOnFiber(
1943 + finishedWork: Fiber,
1944 + root: FiberRoot,
1945 + renderPriorityLevel: ReactPriorityLevel,
1946 +) {
1947 + const flags = finishedWork.flags;
1948 +
1949 + if (flags & ContentReset) {
1950 + commitResetTextContent(finishedWork);
1951 + }
1952 +
1953 + if (flags & Ref) {
1954 + const current = finishedWork.alternate;
1955 + if (current !== null) {
1956 + commitDetachRef(current);
1957 + }
1958 + if (enableScopeAPI) {
1959 + // TODO: This is a temporary solution that allowed us to transition away
1960 + // from React Flare on www.
1961 + if (finishedWork.tag === ScopeComponent) {
1962 + commitAttachRef(finishedWork);
1963 + }
1964 + }
1965 + }
1966 +
1967 + // The following switch statement is only concerned about placement,
1968 + // updates, and deletions. To avoid needing to add a case for every possible
1969 + // bitmap value, we remove the secondary effects from the effect tag and
1970 + // switch on that value.
1971 + const primaryFlags = flags & (Placement | Update | Hydrating);
1972 + outer: switch (primaryFlags) {
1973 + case Placement: {
1974 + commitPlacement(finishedWork);
1975 + // Clear the "placement" from effect tag so that we know that this is
1976 + // inserted, before any life-cycles like componentDidMount gets called.
1977 + // TODO: findDOMNode doesn't rely on this any more but isMounted does
1978 + // and isMounted is deprecated anyway so we should be able to kill this.
1979 + finishedWork.flags &= ~Placement;
1980 + break;
1981 + }
1982 + case PlacementAndUpdate: {
1983 + // Placement
1984 + commitPlacement(finishedWork);
1985 + // Clear the "placement" from effect tag so that we know that this is
1986 + // inserted, before any life-cycles like componentDidMount gets called.
1987 + finishedWork.flags &= ~Placement;
1988 +
1989 + // Update
1990 + const current = finishedWork.alternate;
1991 + commitWork(current, finishedWork);
1992 + break;
1993 + }
1994 + case Hydrating: {
1995 + finishedWork.flags &= ~Hydrating;
1996 + break;
1997 + }
1998 + case HydratingAndUpdate: {
1999 + finishedWork.flags &= ~Hydrating;
2000 +
2001 + // Update
2002 + const current = finishedWork.alternate;
2003 + commitWork(current, finishedWork);
2004 + break;
2005 + }
2006 + case Update: {
2007 + const current = finishedWork.alternate;
2008 + commitWork(current, finishedWork);
2009 + break;
2010 + }
2011 + }
2012 +}
2013 +
2014 export function commitLayoutEffects(
2015 finishedWork: Fiber,
2016 root: FiberRoot,
packages/react-reconciler/src/ReactFiberHydrationContext.new.js
+2 -3
@@ -144,14 +144,13 @@ function deleteHydratableInstance(
144 returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
145 }
146
147 - let deletions = returnFiber.deletions;
147 + const deletions = returnFiber.deletions;
148 if (deletions === null) {
149 - deletions = returnFiber.deletions = [childToDelete];
149 + returnFiber.deletions = [childToDelete];
150 returnFiber.flags |= ChildDeletion;
151 } else {
152 deletions.push(childToDelete);
153 }
154 - childToDelete.deletions = deletions;
154 }
155
156 function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+2 -150
@@ -31,7 +31,6 @@ import {
31 decoupleUpdatePriorityFromScheduler,
32 enableDebugTracing,
33 enableSchedulingProfiler,
34 - enableScopeAPI,
34 disableSchedulerTimeoutInWorkLoop,
35 } from 'shared/ReactFeatureFlags';
36 import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -116,7 +115,6 @@ import {
115 ForwardRef,
116 MemoComponent,
117 SimpleMemoComponent,
119 - ScopeComponent,
118 Profiler,
119 } from './ReactWorkTags';
120 import {LegacyRoot} from './ReactRootTags';
@@ -124,19 +122,14 @@ import {
122 NoFlags,
123 PerformedWork,
124 Placement,
127 - Update,
128 - PlacementAndUpdate,
125 Deletion,
126 ChildDeletion,
131 - Ref,
132 - ContentReset,
127 Snapshot,
128 Passive,
129 PassiveStatic,
130 Incomplete,
131 HostEffectMask,
132 Hydrating,
139 - HydratingAndUpdate,
133 StaticMask,
134 } from './ReactFiberFlags';
135 import {
@@ -190,13 +183,8 @@ import {
183 import {
184 commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
185 commitLayoutEffects,
193 - commitPlacement,
194 - commitWork,
195 - commitDeletion,
196 - commitDetachRef,
197 - commitAttachRef,
186 + commitMutationEffects,
187 commitPassiveEffectDurations,
199 - commitResetTextContent,
188 isSuspenseBoundaryBeingHidden,
189 commitPassiveMountEffects,
190 commitPassiveUnmountEffects,
@@ -2031,32 +2019,7 @@ function commitRootImpl(root, renderPriorityLevel) {
2019 }
2020
2021 // The next phase is the mutation phase, where we mutate the host tree.
2034 - nextEffect = firstEffect;
2035 - do {
2036 - if (__DEV__) {
2037 - invokeGuardedCallback(
2038 - null,
2039 - commitMutationEffects,
2040 - null,
2041 - root,
2042 - renderPriorityLevel,
2043 - );
2044 - if (hasCaughtError()) {
2045 - invariant(nextEffect !== null, 'Should be working on an effect.');
2046 - const error = clearCaughtError();
2047 - captureCommitPhaseError(nextEffect, error);
2048 - nextEffect = nextEffect.nextEffect;
2049 - }
2050 - } else {
2051 - try {
2052 - commitMutationEffects(root, renderPriorityLevel);
2053 - } catch (error) {
2054 - invariant(nextEffect !== null, 'Should be working on an effect.');
2055 - captureCommitPhaseError(nextEffect, error);
2056 - nextEffect = nextEffect.nextEffect;
2057 - }
2058 - }
2059 - } while (nextEffect !== null);
2022 + commitMutationEffects(root, renderPriorityLevel, finishedWork);
2023
2024 if (shouldFireAfterActiveInstanceBlur) {
2025 afterActiveInstanceBlur();
@@ -2304,117 +2267,6 @@ function commitBeforeMutationEffects() {
2267 }
2268 }
2269
2307 -function commitMutationEffects(
2308 - root: FiberRoot,
2309 - renderPriorityLevel: ReactPriorityLevel,
2310 -) {
2311 - // TODO: Should probably move the bulk of this function to commitWork.
2312 - while (nextEffect !== null) {
2313 - setCurrentDebugFiberInDEV(nextEffect);
2314 -
2315 - const flags = nextEffect.flags;
2316 -
2317 - if (flags & ContentReset) {
2318 - commitResetTextContent(nextEffect);
2319 - }
2320 -
2321 - if (flags & Ref) {
2322 - const current = nextEffect.alternate;
2323 - if (current !== null) {
2324 - commitDetachRef(current);
2325 - }
2326 - if (enableScopeAPI) {
2327 - // TODO: This is a temporary solution that allowed us to transition away
2328 - // from React Flare on www.
2329 - if (nextEffect.tag === ScopeComponent) {
2330 - commitAttachRef(nextEffect);
2331 - }
2332 - }
2333 - }
2334 -
2335 - // The following switch statement is only concerned about placement,
2336 - // updates, and deletions. To avoid needing to add a case for every possible
2337 - // bitmap value, we remove the secondary effects from the effect tag and
2338 - // switch on that value.
2339 - const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
2340 - outer: switch (primaryFlags) {
2341 - case Placement: {
2342 - commitPlacement(nextEffect);
2343 - // Clear the "placement" from effect tag so that we know that this is
2344 - // inserted, before any life-cycles like componentDidMount gets called.
2345 - // TODO: findDOMNode doesn't rely on this any more but isMounted does
2346 - // and isMounted is deprecated anyway so we should be able to kill this.
2347 - nextEffect.flags &= ~Placement;
2348 - break;
2349 - }
2350 - case PlacementAndUpdate: {
2351 - // Placement
2352 - commitPlacement(nextEffect);
2353 - // Clear the "placement" from effect tag so that we know that this is
2354 - // inserted, before any life-cycles like componentDidMount gets called.
2355 - nextEffect.flags &= ~Placement;
2356 -
2357 - // Update
2358 - const current = nextEffect.alternate;
2359 - commitWork(current, nextEffect);
2360 - break;
2361 - }
2362 - case Hydrating: {
2363 - nextEffect.flags &= ~Hydrating;
2364 - break;
2365 - }
2366 - case HydratingAndUpdate: {
2367 - nextEffect.flags &= ~Hydrating;
2368 -
2369 - // Update
2370 - const current = nextEffect.alternate;
2371 - commitWork(current, nextEffect);
2372 - break;
2373 - }
2374 - case Update: {
2375 - const current = nextEffect.alternate;
2376 - commitWork(current, nextEffect);
2377 - break;
2378 - }
2379 - case Deletion: {
2380 - // Reached a deletion effect. Instead of commit this effect like we
2381 - // normally do, we're going to use the `deletions` array of the parent.
2382 - // However, because the effect list is sorted in depth-first order, we
2383 - // can't wait until we reach the parent node, because the child effects
2384 - // will have run in the meantime.
2385 - //
2386 - // So instead, we use a trick where the first time we hit a deletion
2387 - // effect, we commit all the deletion effects that belong to that parent.
2388 - //
2389 - // This is an incremental step away from using the effect list and
2390 - // toward a DFS + subtreeFlags traversal.
2391 - //
2392 - // A reference to the deletion array of the parent is also stored on
2393 - // each of the deletions. This is really weird. It would be better to
2394 - // follow the `.return` pointer, but unfortunately we can't assume that
2395 - // `.return` points to the correct fiber, even in the commit phase,
2396 - // because `findDOMNode` might mutate it.
2397 - const deletedChild = nextEffect;
2398 - const deletions = deletedChild.deletions;
2399 - if (deletions !== null) {
2400 - for (let i = 0; i < deletions.length; i++) {
2401 - const deletion = deletions[i];
2402 - // Clear the deletion effect so that we don't delete this node more
2403 - // than once.
2404 - deletion.flags &= ~Deletion;
2405 - deletion.deletions = null;
2406 - commitDeletion(root, deletion, renderPriorityLevel);
2407 - }
2408 - }
2409 - break;
2410 - }
2411 - }
2412 -
2413 - resetCurrentDebugFiberInDEV();
2414 - nextEffect = nextEffect.nextEffect;
2415 - }
2416 -}
2417 -
2270 export function flushPassiveEffects(): boolean {
2271 // Returns whether passive effects were flushed.
2272 if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {