Skip to content

Commit 89c779d

Browse files
committed
Use recursion to traverse during mutation phase
Most of the commit phase uses iterative loops to traverse the tree. Originally we thought this would be faster than using recursion, but a while back @trueadm did some performance testing and found that the loop was slower because we assign to the `return` pointer before entering a subtree (which we have to do because the `return` pointer is not always consistent; it could point to one of two fibers). The other motivation is so we can take advantage of the JS stack to track contextual information, like the nearest host parent. We already use recursion in a few places; this changes the mutation phase to use it, too.
1 parent 6760039 commit 89c779d

File tree

3 files changed

+108
-86
lines changed

3 files changed

+108
-86
lines changed

packages/react-reconciler/src/ReactCurrentFiber.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,22 @@ export function resetCurrentFiber() {
5151
}
5252
}
5353

54-
export function setCurrentFiber(fiber: Fiber) {
54+
export function setCurrentFiber(fiber: Fiber | null) {
5555
if (__DEV__) {
56-
ReactDebugCurrentFrame.getCurrentStack = getCurrentFiberStackInDev;
56+
ReactDebugCurrentFrame.getCurrentStack =
57+
fiber === null ? null : getCurrentFiberStackInDev;
5758
current = fiber;
5859
isRendering = false;
5960
}
6061
}
6162

63+
export function getCurrentFiber(): Fiber | null {
64+
if (__DEV__) {
65+
return current;
66+
}
67+
return null;
68+
}
69+
6270
export function setIsRendering(rendering: boolean) {
6371
if (__DEV__) {
6472
isRendering = rendering;

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

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFrom
8484
import {
8585
resetCurrentFiber as resetCurrentDebugFiberInDEV,
8686
setCurrentFiber as setCurrentDebugFiberInDEV,
87+
getCurrentFiber as getCurrentDebugFiberInDEV,
8788
} from './ReactCurrentFiber';
8889
import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
8990
import {
@@ -1897,62 +1898,50 @@ export function isSuspenseBoundaryBeingHidden(
18971898

18981899
export function commitMutationEffects(
18991900
root: FiberRoot,
1900-
firstChild: Fiber,
1901+
finishedWork: Fiber,
19011902
committedLanes: Lanes,
19021903
) {
19031904
inProgressLanes = committedLanes;
19041905
inProgressRoot = root;
1905-
nextEffect = firstChild;
1906+
nextEffect = finishedWork;
19061907

1907-
commitMutationEffects_begin(root, committedLanes);
1908+
setCurrentDebugFiberInDEV(finishedWork);
1909+
commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
1910+
setCurrentDebugFiberInDEV(finishedWork);
19081911

19091912
inProgressLanes = null;
19101913
inProgressRoot = null;
19111914
}
19121915

1913-
function commitMutationEffects_begin(root: FiberRoot, lanes: Lanes) {
1914-
while (nextEffect !== null) {
1915-
const fiber = nextEffect;
1916-
1917-
// TODO: Should wrap this in flags check, too, as optimization
1918-
const deletions = fiber.deletions;
1919-
if (deletions !== null) {
1920-
for (let i = 0; i < deletions.length; i++) {
1921-
const childToDelete = deletions[i];
1922-
try {
1923-
commitDeletion(root, childToDelete, fiber);
1924-
} catch (error) {
1925-
captureCommitPhaseError(childToDelete, fiber, error);
1926-
}
1916+
function recursivelyTraverseMutationEffects(
1917+
root: FiberRoot,
1918+
parentFiber: Fiber,
1919+
lanes: Lanes,
1920+
) {
1921+
// Deletions effects can be scheduled on any fiber type. They need to happen
1922+
// before the children effects hae fired.
1923+
const deletions = parentFiber.deletions;
1924+
if (deletions !== null) {
1925+
for (let i = 0; i < deletions.length; i++) {
1926+
const childToDelete = deletions[i];
1927+
try {
1928+
commitDeletion(root, childToDelete, parentFiber);
1929+
} catch (error) {
1930+
captureCommitPhaseError(childToDelete, parentFiber, error);
19271931
}
19281932
}
1929-
1930-
const child = fiber.child;
1931-
if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
1932-
child.return = fiber;
1933-
nextEffect = child;
1934-
} else {
1935-
commitMutationEffects_complete(root, lanes);
1936-
}
19371933
}
1938-
}
19391934

1940-
function commitMutationEffects_complete(root: FiberRoot, lanes: Lanes) {
1941-
while (nextEffect !== null) {
1942-
const fiber = nextEffect;
1943-
setCurrentDebugFiberInDEV(fiber);
1944-
commitMutationEffectsOnFiber(fiber, root, lanes);
1945-
resetCurrentDebugFiberInDEV();
1946-
1947-
const sibling = fiber.sibling;
1948-
if (sibling !== null) {
1949-
sibling.return = fiber.return;
1950-
nextEffect = sibling;
1951-
return;
1935+
const prevDebugFiber = getCurrentDebugFiberInDEV();
1936+
if (parentFiber.subtreeFlags & MutationMask) {
1937+
let child = parentFiber.child;
1938+
while (child !== null) {
1939+
setCurrentDebugFiberInDEV(child);
1940+
commitMutationEffectsOnFiber(child, root, lanes);
1941+
child = child.sibling;
19521942
}
1953-
1954-
nextEffect = fiber.return;
19551943
}
1944+
setCurrentDebugFiberInDEV(prevDebugFiber);
19561945
}
19571946

19581947
function commitMutationEffectsOnFiber(
@@ -1971,6 +1960,7 @@ function commitMutationEffectsOnFiber(
19711960
case ForwardRef:
19721961
case MemoComponent:
19731962
case SimpleMemoComponent: {
1963+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
19741964
commitReconciliationEffects(finishedWork);
19751965

19761966
if (flags & Update) {
@@ -2023,6 +2013,7 @@ function commitMutationEffectsOnFiber(
20232013
return;
20242014
}
20252015
case ClassComponent: {
2016+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
20262017
commitReconciliationEffects(finishedWork);
20272018

20282019
if (flags & Ref) {
@@ -2033,6 +2024,7 @@ function commitMutationEffectsOnFiber(
20332024
return;
20342025
}
20352026
case HostComponent: {
2027+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
20362028
commitReconciliationEffects(finishedWork);
20372029

20382030
if (flags & Ref) {
@@ -2041,7 +2033,13 @@ function commitMutationEffectsOnFiber(
20412033
}
20422034
}
20432035
if (supportsMutation) {
2044-
if (flags & ContentReset) {
2036+
// TODO: ContentReset gets cleared by the children during the commit
2037+
// phase. This is a refactor hazard because it means we must read
2038+
// flags the flags after `commitReconciliationEffects` has already run;
2039+
// the order matters. We should refactor so that ContentReset does not
2040+
// rely on mutating the flag during commit. Like by setting a flag
2041+
// during the render phase instead.
2042+
if (finishedWork.flags & ContentReset) {
20452043
const instance: Instance = finishedWork.stateNode;
20462044
try {
20472045
resetTextContent(instance);
@@ -2088,6 +2086,7 @@ function commitMutationEffectsOnFiber(
20882086
return;
20892087
}
20902088
case HostText: {
2089+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
20912090
commitReconciliationEffects(finishedWork);
20922091

20932092
if (flags & Update) {
@@ -2117,6 +2116,7 @@ function commitMutationEffectsOnFiber(
21172116
return;
21182117
}
21192118
case HostRoot: {
2119+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
21202120
commitReconciliationEffects(finishedWork);
21212121

21222122
// TODO: Add a flag check and move to passive phase. Luna's PR fixes this.
@@ -2176,6 +2176,7 @@ function commitMutationEffectsOnFiber(
21762176
return;
21772177
}
21782178
case HostPortal: {
2179+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
21792180
commitReconciliationEffects(finishedWork);
21802181

21812182
if (flags & Update) {
@@ -2193,6 +2194,7 @@ function commitMutationEffectsOnFiber(
21932194
return;
21942195
}
21952196
case SuspenseComponent: {
2197+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
21962198
commitReconciliationEffects(finishedWork);
21972199

21982200
if (flags & Visibility) {
@@ -2217,6 +2219,7 @@ function commitMutationEffectsOnFiber(
22172219
return;
22182220
}
22192221
case OffscreenComponent: {
2222+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
22202223
commitReconciliationEffects(finishedWork);
22212224

22222225
if (flags & Visibility) {
@@ -2254,6 +2257,7 @@ function commitMutationEffectsOnFiber(
22542257
return;
22552258
}
22562259
case SuspenseListComponent: {
2260+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
22572261
commitReconciliationEffects(finishedWork);
22582262

22592263
if (flags & Update) {
@@ -2262,6 +2266,7 @@ function commitMutationEffectsOnFiber(
22622266
return;
22632267
}
22642268
case ScopeComponent: {
2269+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
22652270
commitReconciliationEffects(finishedWork);
22662271

22672272
if (enableScopeAPI) {
@@ -2278,11 +2283,13 @@ function commitMutationEffectsOnFiber(
22782283
return;
22792284
}
22802285
default: {
2286+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
22812287
commitReconciliationEffects(finishedWork);
2288+
2289+
return;
22822290
}
22832291
}
22842292
}
2285-
22862293
function commitReconciliationEffects(finishedWork: Fiber) {
22872294
// Placement effects (insertions, reorders) can be scheduled on any fiber
22882295
// type. They needs to happen after the children effects have fired, but

0 commit comments

Comments
 (0)