@samitouri / QOS-React / commits / d93c8faada

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 }