Skip to content

Commit 481dece

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 f9e6aef commit 481dece

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 {
@@ -1901,62 +1902,50 @@ export function isSuspenseBoundaryBeingHidden(
19011902

19021903
export function commitMutationEffects(
19031904
root: FiberRoot,
1904-
firstChild: Fiber,
1905+
finishedWork: Fiber,
19051906
committedLanes: Lanes,
19061907
) {
19071908
inProgressLanes = committedLanes;
19081909
inProgressRoot = root;
1909-
nextEffect = firstChild;
1910+
nextEffect = finishedWork;
19101911

1911-
commitMutationEffects_begin(root, committedLanes);
1912+
setCurrentDebugFiberInDEV(finishedWork);
1913+
commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
1914+
setCurrentDebugFiberInDEV(finishedWork);
19121915

19131916
inProgressLanes = null;
19141917
inProgressRoot = null;
19151918
}
19161919

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

1944-
function commitMutationEffects_complete(root: FiberRoot, lanes: Lanes) {
1945-
while (nextEffect !== null) {
1946-
const fiber = nextEffect;
1947-
setCurrentDebugFiberInDEV(fiber);
1948-
commitMutationEffectsOnFiber(fiber, root, lanes);
1949-
resetCurrentDebugFiberInDEV();
1950-
1951-
const sibling = fiber.sibling;
1952-
if (sibling !== null) {
1953-
sibling.return = fiber.return;
1954-
nextEffect = sibling;
1955-
return;
1939+
const prevDebugFiber = getCurrentDebugFiberInDEV();
1940+
if (parentFiber.subtreeFlags & MutationMask) {
1941+
let child = parentFiber.child;
1942+
while (child !== null) {
1943+
setCurrentDebugFiberInDEV(child);
1944+
commitMutationEffectsOnFiber(child, root, lanes);
1945+
child = child.sibling;
19561946
}
1957-
1958-
nextEffect = fiber.return;
19591947
}
1948+
setCurrentDebugFiberInDEV(prevDebugFiber);
19601949
}
19611950

19621951
function commitMutationEffectsOnFiber(
@@ -1975,6 +1964,7 @@ function commitMutationEffectsOnFiber(
19751964
case ForwardRef:
19761965
case MemoComponent:
19771966
case SimpleMemoComponent: {
1967+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
19781968
commitReconciliationEffects(finishedWork);
19791969

19801970
if (flags & Update) {
@@ -2027,6 +2017,7 @@ function commitMutationEffectsOnFiber(
20272017
return;
20282018
}
20292019
case ClassComponent: {
2020+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
20302021
commitReconciliationEffects(finishedWork);
20312022

20322023
if (flags & Ref) {
@@ -2037,6 +2028,7 @@ function commitMutationEffectsOnFiber(
20372028
return;
20382029
}
20392030
case HostComponent: {
2031+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
20402032
commitReconciliationEffects(finishedWork);
20412033

20422034
if (flags & Ref) {
@@ -2045,7 +2037,13 @@ function commitMutationEffectsOnFiber(
20452037
}
20462038
}
20472039
if (supportsMutation) {
2048-
if (flags & ContentReset) {
2040+
// TODO: ContentReset gets cleared by the children during the commit
2041+
// phase. This is a refactor hazard because it means we must read
2042+
// flags the flags after `commitReconciliationEffects` has already run;
2043+
// the order matters. We should refactor so that ContentReset does not
2044+
// rely on mutating the flag during commit. Like by setting a flag
2045+
// during the render phase instead.
2046+
if (finishedWork.flags & ContentReset) {
20492047
const instance: Instance = finishedWork.stateNode;
20502048
try {
20512049
resetTextContent(instance);
@@ -2092,6 +2090,7 @@ function commitMutationEffectsOnFiber(
20922090
return;
20932091
}
20942092
case HostText: {
2093+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
20952094
commitReconciliationEffects(finishedWork);
20962095

20972096
if (flags & Update) {
@@ -2121,6 +2120,7 @@ function commitMutationEffectsOnFiber(
21212120
return;
21222121
}
21232122
case HostRoot: {
2123+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
21242124
commitReconciliationEffects(finishedWork);
21252125

21262126
if (flags & Update) {
@@ -2153,6 +2153,7 @@ function commitMutationEffectsOnFiber(
21532153
return;
21542154
}
21552155
case HostPortal: {
2156+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
21562157
commitReconciliationEffects(finishedWork);
21572158

21582159
if (flags & Update) {
@@ -2170,6 +2171,7 @@ function commitMutationEffectsOnFiber(
21702171
return;
21712172
}
21722173
case SuspenseComponent: {
2174+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
21732175
commitReconciliationEffects(finishedWork);
21742176

21752177
if (flags & Visibility) {
@@ -2194,6 +2196,7 @@ function commitMutationEffectsOnFiber(
21942196
return;
21952197
}
21962198
case OffscreenComponent: {
2199+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
21972200
commitReconciliationEffects(finishedWork);
21982201

21992202
if (flags & Visibility) {
@@ -2231,6 +2234,7 @@ function commitMutationEffectsOnFiber(
22312234
return;
22322235
}
22332236
case SuspenseListComponent: {
2237+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
22342238
commitReconciliationEffects(finishedWork);
22352239

22362240
if (flags & Update) {
@@ -2240,6 +2244,7 @@ function commitMutationEffectsOnFiber(
22402244
}
22412245
case ScopeComponent: {
22422246
if (enableScopeAPI) {
2247+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
22432248
commitReconciliationEffects(finishedWork);
22442249

22452250
// TODO: This is a temporary solution that allowed us to transition away
@@ -2258,11 +2263,13 @@ function commitMutationEffectsOnFiber(
22582263
return;
22592264
}
22602265
default: {
2266+
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
22612267
commitReconciliationEffects(finishedWork);
2268+
2269+
return;
22622270
}
22632271
}
22642272
}
2265-
22662273
function commitReconciliationEffects(finishedWork: Fiber) {
22672274
// Placement effects (insertions, reorders) can be scheduled on any fiber
22682275
// type. They needs to happen after the children effects have fired, but

0 commit comments

Comments
 (0)