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) {