Move Persistent Mode Optimization away from firstEffect (#19381)
Persistent mode needs to clone a parent and add its children if a child has changed. We have an optimization in persistent mode where we don't do that if no child could've changed. If there are no effects scheduled for any child then there couldn't have been changes. Instead of checking for this on firstEffect, we now check this on the children's effectTag and subtreeTags. This is quite unfortunate because if we could just do this check a little bit later we would've already gotten it transferred to the completed work's subtreeTag. Now we have to loop over all the children and if any of them changed, we have to loop over them again. Doing at least two loops per parent.
Sebastian Markbåge committed
Jul 24, 2020 at 12:31 UTC
d93c8faadac0a1cea4ba2a3c1333e78314a6e61a
1 file changed
+28
-7
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+28
-7
@@ -65,7 +65,9 @@ import {
65
NoEffect,
66
DidCapture,
67
Snapshot,
68
+ MutationMask,
69
} from './ReactSideEffectTags';
70
+import {NoEffect as NoSubtreeTag, Mutation} from './ReactSubtreeTags';
71
import invariant from 'shared/invariant';
72
73
import {
@@ -154,6 +156,25 @@ function markRef(workInProgress: Fiber) {
156
workInProgress.effectTag |= Ref;
157
}
158
159
+function hadNoMutationsEffects(current: null | Fiber, completedWork: Fiber) {
160
+ const didBailout = current !== null && current.child === completedWork.child;
161
+ if (didBailout) {
162
+ return true;
163
+ }
164
+
165
+ let child = completedWork.child;
166
+ while (child !== null) {
167
+ if ((child.effectTag & MutationMask) !== NoEffect) {
168
+ return false;
169
+ }
170
+ if ((child.subtreeTag & Mutation) !== NoSubtreeTag) {
171
+ return false;
172
+ }
173
+ child = child.sibling;
174
+ }
175
+ return true;
176
+}
177
+
178
let appendAllChildren;
179
let updateHostContainer;
180
let updateHostComponent;
@@ -198,7 +219,7 @@ if (supportsMutation) {
219
}
220
};
221
201
- updateHostContainer = function(workInProgress: Fiber) {
222
+ updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
223
// Noop
224
};
225
updateHostComponent = function(
@@ -442,13 +463,13 @@ if (supportsMutation) {
463
node = node.sibling;
464
}
465
};
445
- updateHostContainer = function(workInProgress: Fiber) {
466
+ updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
467
const portalOrRoot: {
468
containerInfo: Container,
469
pendingChildren: ChildSet,
470
...
471
} = workInProgress.stateNode;
451
- const childrenUnchanged = workInProgress.firstEffect === null;
472
+ const childrenUnchanged = hadNoMutationsEffects(current, workInProgress);
473
if (childrenUnchanged) {
474
// No changes, just reuse the existing instance.
475
} else {
@@ -473,7 +494,7 @@ if (supportsMutation) {
494
const oldProps = current.memoizedProps;
495
// If there are no effects associated with this node, then none of our children had any updates.
496
// This guarantees that we can reuse all of them.
476
- const childrenUnchanged = workInProgress.firstEffect === null;
497
+ const childrenUnchanged = hadNoMutationsEffects(current, workInProgress);
498
if (childrenUnchanged && oldProps === newProps) {
499
// No changes, just reuse the existing instance.
500
// Note that this might release a previous clone.
@@ -556,7 +577,7 @@ if (supportsMutation) {
577
};
578
} else {
579
// No host operations
559
- updateHostContainer = function(workInProgress: Fiber) {
580
+ updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
581
// Noop
582
};
583
updateHostComponent = function(
@@ -700,7 +721,7 @@ function completeWork(
721
workInProgress.effectTag |= Snapshot;
722
}
723
}
703
- updateHostContainer(workInProgress);
724
+ updateHostContainer(current, workInProgress);
725
return null;
726
}
727
case HostComponent: {
@@ -979,7 +1000,7 @@ function completeWork(
1000
}
1001
case HostPortal:
1002
popHostContainer(workInProgress);
982
- updateHostContainer(workInProgress);
1003
+ updateHostContainer(current, workInProgress);
1004
if (current === null) {
1005
preparePortalMount(workInProgress.stateNode.containerInfo);
1006
}