Skip to content

Commit 95feb0e

Browse files
authored
Convert mutation phase to depth-first traversal (#20596)
1 parent 6132919 commit 95feb0e

5 files changed

+180
-170
lines changed

packages/react-reconciler/src/ReactChildFiber.new.js

+2-11
Original file line numberDiff line numberDiff line change
@@ -282,22 +282,13 @@ function ChildReconciler(shouldTrackSideEffects) {
282282
childToDelete.nextEffect = null;
283283
childToDelete.flags = (childToDelete.flags & StaticMask) | Deletion;
284284

285-
let deletions = returnFiber.deletions;
285+
const deletions = returnFiber.deletions;
286286
if (deletions === null) {
287-
deletions = returnFiber.deletions = [childToDelete];
287+
returnFiber.deletions = [childToDelete];
288288
returnFiber.flags |= ChildDeletion;
289289
} else {
290290
deletions.push(childToDelete);
291291
}
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;
301292
}
302293

303294
function deleteRemainingChildren(

packages/react-reconciler/src/ReactFiberBeginWork.new.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -2203,14 +2203,13 @@ function updateSuspensePrimaryChildren(
22032203
currentFallbackChildFragment.flags =
22042204
(currentFallbackChildFragment.flags & StaticMask) | Deletion;
22052205
workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChildFragment;
2206-
let deletions = workInProgress.deletions;
2206+
const deletions = workInProgress.deletions;
22072207
if (deletions === null) {
2208-
deletions = workInProgress.deletions = [currentFallbackChildFragment];
2208+
workInProgress.deletions = [currentFallbackChildFragment];
22092209
workInProgress.flags |= ChildDeletion;
22102210
} else {
22112211
deletions.push(currentFallbackChildFragment);
22122212
}
2213-
currentFallbackChildFragment.deletions = deletions;
22142213
}
22152214

22162215
workInProgress.child = primaryChildFragment;
@@ -3194,14 +3193,13 @@ function remountFiber(
31943193
current.nextEffect = null;
31953194
current.flags = (current.flags & StaticMask) | Deletion;
31963195

3197-
let deletions = returnFiber.deletions;
3196+
const deletions = returnFiber.deletions;
31983197
if (deletions === null) {
3199-
deletions = returnFiber.deletions = [current];
3198+
returnFiber.deletions = [current];
32003199
returnFiber.flags |= ChildDeletion;
32013200
} else {
32023201
deletions.push(current);
32033202
}
3204-
current.deletions = deletions;
32053203

32063204
newWorkInProgress.flags |= Placement;
32073205

packages/react-reconciler/src/ReactFiberCommitWork.new.js

+170
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,16 @@ import {
6666
NoFlags,
6767
ContentReset,
6868
Placement,
69+
PlacementAndUpdate,
6970
ChildDeletion,
7071
Snapshot,
7172
Update,
7273
Callback,
7374
Ref,
75+
Hydrating,
76+
HydratingAndUpdate,
7477
Passive,
78+
MutationMask,
7579
PassiveMask,
7680
LayoutMask,
7781
PassiveUnmountPendingDev,
@@ -1841,6 +1845,172 @@ function commitResetTextContent(current: Fiber) {
18411845
resetTextContent(current.stateNode);
18421846
}
18431847

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+
18442014
export function commitLayoutEffects(
18452015
finishedWork: Fiber,
18462016
root: FiberRoot,

packages/react-reconciler/src/ReactFiberHydrationContext.new.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,13 @@ function deleteHydratableInstance(
144144
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
145145
}
146146

147-
let deletions = returnFiber.deletions;
147+
const deletions = returnFiber.deletions;
148148
if (deletions === null) {
149-
deletions = returnFiber.deletions = [childToDelete];
149+
returnFiber.deletions = [childToDelete];
150150
returnFiber.flags |= ChildDeletion;
151151
} else {
152152
deletions.push(childToDelete);
153153
}
154-
childToDelete.deletions = deletions;
155154
}
156155

157156
function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {

0 commit comments

Comments
 (0)