@samitouri / QOS-React-2 / commits / 0fd6805c6d

Land rest of effects refactor in main fork (#20644)

Andrew Clark committed Jan 22, 2021 at 13:49 UTC 0fd6805c6d0477be917d31949a73b3bbdccfe71c
11 files changed +234 -392
.eslintrc.js
+1 -1
@@ -116,7 +116,7 @@ module.exports = {
116 'react-internal/no-cross-fork-types': [
117 ERROR,
118 {
119 - old: ['firstEffect', 'nextEffect'],
119 + old: [],
120 new: [],
121 },
122 ],
packages/react-reconciler/src/ReactChildFiber.old.js
+1 -21
@@ -13,12 +13,7 @@ import type {Fiber} from './ReactInternalTypes';
13 import type {Lanes} from './ReactFiberLane.old';
14
15 import getComponentName from 'shared/getComponentName';
16 -import {
17 - Deletion,
18 - ChildDeletion,
19 - Placement,
20 - StaticMask,
21 -} from './ReactFiberFlags';
16 +import {Placement, ChildDeletion} from './ReactFiberFlags';
17 import {
18 getIteratorFn,
19 REACT_ELEMENT_TYPE,
@@ -268,21 +263,6 @@ function ChildReconciler(shouldTrackSideEffects) {
263 // Noop.
264 return;
265 }
271 - // Deletions are added in reversed order so we add it to the front.
272 - // At this point, the return fiber's effect list is empty except for
273 - // deletions, so we can just append the deletion to the list. The remaining
274 - // effects aren't added until the complete phase. Once we implement
275 - // resuming, this may not be true.
276 - const last = returnFiber.lastEffect;
277 - if (last !== null) {
278 - last.nextEffect = childToDelete;
279 - returnFiber.lastEffect = childToDelete;
280 - } else {
281 - returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
282 - }
283 - childToDelete.nextEffect = null;
284 - childToDelete.flags = (childToDelete.flags & StaticMask) | Deletion;
285 -
266 const deletions = returnFiber.deletions;
267 if (deletions === null) {
268 returnFiber.deletions = [childToDelete];
packages/react-reconciler/src/ReactFiber.old.js
+5 -15
@@ -144,10 +144,6 @@ function FiberNode(
144
145 // Effects
146 this.flags = NoFlags;
147 - this.nextEffect = null;
148 -
149 - this.firstEffect = null;
150 - this.lastEffect = null;
147 this.subtreeFlags = NoFlags;
148 this.deletions = null;
149
@@ -285,10 +281,7 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
281 // Reset the effect tag.
282 workInProgress.flags = NoFlags;
283
288 - // The effect list is no longer valid.
289 - workInProgress.nextEffect = null;
290 - workInProgress.firstEffect = null;
291 - workInProgress.lastEffect = null;
284 + // The effects are no longer valid.
285 workInProgress.subtreeFlags = NoFlags;
286 workInProgress.deletions = null;
287
@@ -370,10 +363,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
363 // that child fiber is setting, not the reconciliation.
364 workInProgress.flags &= StaticMask | Placement;
365
373 - // The effect list is no longer valid.
374 - workInProgress.nextEffect = null;
375 - workInProgress.firstEffect = null;
376 - workInProgress.lastEffect = null;
366 + // The effects are no longer valid.
367
368 const current = workInProgress.alternate;
369 if (current === null) {
@@ -403,6 +393,9 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
393 workInProgress.lanes = current.lanes;
394
395 workInProgress.child = current.child;
396 + // TODO: `subtreeFlags` should be reset to NoFlags, like we do in
397 + // `createWorkInProgress`. Nothing reads this until the complete phase,
398 + // currently, but it might in the future, and we should be consistent.
399 workInProgress.subtreeFlags = current.subtreeFlags;
400 workInProgress.deletions = null;
401 workInProgress.memoizedProps = current.memoizedProps;
@@ -847,9 +840,6 @@ export function assignFiberPropertiesInDEV(
840 target.dependencies = source.dependencies;
841 target.mode = source.mode;
842 target.flags = source.flags;
850 - target.nextEffect = source.nextEffect;
851 - target.firstEffect = source.firstEffect;
852 - target.lastEffect = source.lastEffect;
843 target.subtreeFlags = source.subtreeFlags;
844 target.deletions = source.deletions;
845 target.lanes = source.lanes;
packages/react-reconciler/src/ReactFiberBeginWork.old.js
+3 -37
@@ -67,7 +67,6 @@ import {
67 DidCapture,
68 Update,
69 Ref,
70 - Deletion,
70 ChildDeletion,
71 ForceUpdateForLegacySuspense,
72 StaticMask,
@@ -2199,10 +2198,6 @@ function updateSuspensePrimaryChildren(
2198 primaryChildFragment.sibling = null;
2199 if (currentFallbackChildFragment !== null) {
2200 // Delete the fallback child fragment
2202 - currentFallbackChildFragment.nextEffect = null;
2203 - currentFallbackChildFragment.flags =
2204 - (currentFallbackChildFragment.flags & StaticMask) | Deletion;
2205 - workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChildFragment;
2201 const deletions = workInProgress.deletions;
2202 if (deletions === null) {
2203 workInProgress.deletions = [currentFallbackChildFragment];
@@ -2264,22 +2259,9 @@ function updateSuspenseFallbackChildren(
2259 currentPrimaryChildFragment.treeBaseDuration;
2260 }
2261
2267 - if (currentFallbackChildFragment !== null) {
2268 - // The fallback fiber was added as a deletion effect during the first
2269 - // pass. However, since we're going to remain on the fallback, we no
2270 - // longer want to delete it. So we need to remove it from the list.
2271 - // Deletions are stored on the same list as effects, and are always added
2272 - // to the front. So we know that the first effect must be the fallback
2273 - // deletion effect, and everything after that is from the primary free.
2274 - const firstPrimaryTreeEffect = currentFallbackChildFragment.nextEffect;
2275 - if (firstPrimaryTreeEffect !== null) {
2276 - workInProgress.firstEffect = firstPrimaryTreeEffect;
2277 - } else {
2278 - // TODO: Reset this somewhere else? Lol legacy mode is so weird.
2279 - workInProgress.firstEffect = workInProgress.lastEffect = null;
2280 - }
2281 - }
2282 -
2262 + // The fallback fiber was added as a deletion during the first pass.
2263 + // However, since we're going to remain on the fallback, we no longer want
2264 + // to delete it.
2265 workInProgress.deletions = null;
2266 } else {
2267 primaryChildFragment = createWorkInProgressOffscreenFiber(
@@ -2776,7 +2758,6 @@ function initSuspenseListRenderState(
2758 tail: null | Fiber,
2759 lastContentRow: null | Fiber,
2760 tailMode: SuspenseListTailMode,
2779 - lastEffectBeforeRendering: null | Fiber,
2761 ): void {
2762 const renderState: null | SuspenseListRenderState =
2763 workInProgress.memoizedState;
@@ -2788,7 +2769,6 @@ function initSuspenseListRenderState(
2769 last: lastContentRow,
2770 tail: tail,
2771 tailMode: tailMode,
2791 - lastEffect: lastEffectBeforeRendering,
2772 }: SuspenseListRenderState);
2773 } else {
2774 // We can reuse the existing object from previous renders.
@@ -2798,7 +2778,6 @@ function initSuspenseListRenderState(
2778 renderState.last = lastContentRow;
2779 renderState.tail = tail;
2780 renderState.tailMode = tailMode;
2801 - renderState.lastEffect = lastEffectBeforeRendering;
2781 }
2782 }
2783
@@ -2880,7 +2859,6 @@ function updateSuspenseListComponent(
2859 tail,
2860 lastContentRow,
2861 tailMode,
2883 - workInProgress.lastEffect,
2862 );
2863 break;
2864 }
@@ -2912,7 +2890,6 @@ function updateSuspenseListComponent(
2890 tail,
2891 null, // last
2892 tailMode,
2915 - workInProgress.lastEffect,
2893 );
2894 break;
2895 }
@@ -2923,7 +2900,6 @@ function updateSuspenseListComponent(
2900 null, // tail
2901 null, // last
2902 undefined,
2926 - workInProgress.lastEffect,
2903 );
2904 break;
2905 }
@@ -3183,16 +3159,6 @@ function remountFiber(
3159
3160 // Delete the old fiber and place the new one.
3161 // Since the old fiber is disconnected, we have to schedule it manually.
3186 - const last = returnFiber.lastEffect;
3187 - if (last !== null) {
3188 - last.nextEffect = current;
3189 - returnFiber.lastEffect = current;
3190 - } else {
3191 - returnFiber.firstEffect = returnFiber.lastEffect = current;
3192 - }
3193 - current.nextEffect = null;
3194 - current.flags = (current.flags & StaticMask) | Deletion;
3195 -
3162 const deletions = returnFiber.deletions;
3163 if (deletions === null) {
3164 returnFiber.deletions = [current];
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+142 -59
@@ -76,9 +76,10 @@ import {
76 Hydrating,
77 HydratingAndUpdate,
78 Passive,
79 + BeforeMutationMask,
80 MutationMask,
80 - PassiveMask,
81 LayoutMask,
82 + PassiveMask,
83 PassiveUnmountPendingDev,
84 } from './ReactFiberFlags';
85 import getComponentName from 'shared/getComponentName';
@@ -129,13 +130,13 @@ import {
130 commitHydratedSuspenseInstance,
131 clearContainer,
132 prepareScopeUpdate,
133 + prepareForCommit,
134 + beforeActiveInstanceBlur,
135 } from './ReactFiberHostConfig';
136 import {
137 captureCommitPhaseError,
138 resolveRetryWakeable,
139 markCommitTimeOfFallback,
137 - enqueuePendingPassiveHookEffectMount,
138 - enqueuePendingPassiveHookEffectUnmount,
140 enqueuePendingPassiveProfilerEffect,
141 } from './ReactFiberWorkLoop.old';
142 import {
@@ -145,6 +146,7 @@ import {
146 Passive as HookPassive,
147 } from './ReactHookEffectTags';
148 import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.old';
149 +import {doesFiberContain} from './ReactFiberTreeReflection';
150
151 let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
152 if (__DEV__) {
@@ -260,18 +262,114 @@ function safelyCallDestroy(current: Fiber, destroy: () => void) {
262 }
263 }
264
263 -function commitBeforeMutationLifeCycles(
264 - current: Fiber | null,
265 - finishedWork: Fiber,
266 -): void {
267 - switch (finishedWork.tag) {
268 - case FunctionComponent:
269 - case ForwardRef:
270 - case SimpleMemoComponent: {
265 +let focusedInstanceHandle: null | Fiber = null;
266 +let shouldFireAfterActiveInstanceBlur: boolean = false;
267 +
268 +export function commitBeforeMutationEffects(
269 + root: FiberRoot,
270 + firstChild: Fiber,
271 +) {
272 + focusedInstanceHandle = prepareForCommit(root.containerInfo);
273 +
274 + nextEffect = firstChild;
275 + commitBeforeMutationEffects_begin();
276 +
277 + // We no longer need to track the active instance fiber
278 + const shouldFire = shouldFireAfterActiveInstanceBlur;
279 + shouldFireAfterActiveInstanceBlur = false;
280 + focusedInstanceHandle = null;
281 +
282 + return shouldFire;
283 +}
284 +
285 +function commitBeforeMutationEffects_begin() {
286 + while (nextEffect !== null) {
287 + const fiber = nextEffect;
288 +
289 + // TODO: Should wrap this in flags check, too, as optimization
290 + const deletions = fiber.deletions;
291 + if (deletions !== null) {
292 + for (let i = 0; i < deletions.length; i++) {
293 + const deletion = deletions[i];
294 + commitBeforeMutationEffectsDeletion(deletion);
295 + }
296 + }
297 +
298 + const child = fiber.child;
299 + if (
300 + (fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
301 + child !== null
302 + ) {
303 + ensureCorrectReturnPointer(child, fiber);
304 + nextEffect = child;
305 + } else {
306 + commitBeforeMutationEffects_complete();
307 + }
308 + }
309 +}
310 +
311 +function commitBeforeMutationEffects_complete() {
312 + while (nextEffect !== null) {
313 + const fiber = nextEffect;
314 + if (__DEV__) {
315 + setCurrentDebugFiberInDEV(fiber);
316 + invokeGuardedCallback(
317 + null,
318 + commitBeforeMutationEffectsOnFiber,
319 + null,
320 + fiber,
321 + );
322 + if (hasCaughtError()) {
323 + const error = clearCaughtError();
324 + captureCommitPhaseError(fiber, error);
325 + }
326 + resetCurrentDebugFiberInDEV();
327 + } else {
328 + try {
329 + commitBeforeMutationEffectsOnFiber(fiber);
330 + } catch (error) {
331 + captureCommitPhaseError(fiber, error);
332 + }
333 + }
334 +
335 + const sibling = fiber.sibling;
336 + if (sibling !== null) {
337 + ensureCorrectReturnPointer(sibling, fiber.return);
338 + nextEffect = sibling;
339 return;
340 }
273 - case ClassComponent: {
274 - if (finishedWork.flags & Snapshot) {
341 +
342 + nextEffect = fiber.return;
343 + }
344 +}
345 +
346 +function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
347 + const current = finishedWork.alternate;
348 + const flags = finishedWork.flags;
349 +
350 + if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
351 + // Check to see if the focused element was inside of a hidden (Suspense) subtree.
352 + // TODO: Move this out of the hot path using a dedicated effect tag.
353 + if (
354 + finishedWork.tag === SuspenseComponent &&
355 + isSuspenseBoundaryBeingHidden(current, finishedWork) &&
356 + doesFiberContain(finishedWork, focusedInstanceHandle)
357 + ) {
358 + shouldFireAfterActiveInstanceBlur = true;
359 + beforeActiveInstanceBlur(finishedWork);
360 + }
361 + }
362 +
363 + if ((flags & Snapshot) !== NoFlags) {
364 + setCurrentDebugFiberInDEV(finishedWork);
365 +
366 + switch (finishedWork.tag) {
367 + case FunctionComponent:
368 + case ForwardRef:
369 + case SimpleMemoComponent: {
370 + break;
371 + }
372 + case ClassComponent: {
373 if (current !== null) {
374 const prevProps = current.memoizedProps;
375 const prevState = current.memoizedState;
@@ -325,30 +423,43 @@ function commitBeforeMutationLifeCycles(
423 }
424 instance.__reactInternalSnapshotBeforeUpdate = snapshot;
425 }
426 + break;
427 }
329 - return;
330 - }
331 - case HostRoot: {
332 - if (supportsMutation) {
333 - if (finishedWork.flags & Snapshot) {
428 + case HostRoot: {
429 + if (supportsMutation) {
430 const root = finishedWork.stateNode;
431 clearContainer(root.containerInfo);
432 }
433 + break;
434 + }
435 + case HostComponent:
436 + case HostText:
437 + case HostPortal:
438 + case IncompleteClassComponent:
439 + // Nothing to do for these component types
440 + break;
441 + default: {
442 + invariant(
443 + false,
444 + 'This unit of work tag should not have side-effects. This error is ' +
445 + 'likely caused by a bug in React. Please file an issue.',
446 + );
447 }
338 - return;
448 }
340 - case HostComponent:
341 - case HostText:
342 - case HostPortal:
343 - case IncompleteClassComponent:
344 - // Nothing to do for these component types
345 - return;
449 +
450 + resetCurrentDebugFiberInDEV();
451 + }
452 +}
453 +
454 +function commitBeforeMutationEffectsDeletion(deletion: Fiber) {
455 + // TODO (effects) It would be nice to avoid calling doesFiberContain()
456 + // Maybe we can repurpose one of the subtreeFlags positions for this instead?
457 + // Use it to store which part of the tree the focused instance is in?
458 + // This assumes we can safely determine that instance during the "render" phase.
459 + if (doesFiberContain(deletion, ((focusedInstanceHandle: any): Fiber))) {
460 + shouldFireAfterActiveInstanceBlur = true;
461 + beforeActiveInstanceBlur(deletion);
462 }
347 - invariant(
348 - false,
349 - 'This unit of work tag should not have side-effects. This error is ' +
350 - 'likely caused by a bug in React. Please file an issue.',
351 - );
463 }
464
465 function commitHookEffectListUnmount(flags: HookFlags, finishedWork: Fiber) {
@@ -421,26 +532,6 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
532 }
533 }
534
424 -function schedulePassiveEffects(finishedWork: Fiber) {
425 - const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
426 - const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
427 - if (lastEffect !== null) {
428 - const firstEffect = lastEffect.next;
429 - let effect = firstEffect;
430 - do {
431 - const {next, tag} = effect;
432 - if (
433 - (tag & HookPassive) !== NoHookEffect &&
434 - (tag & HookHasEffect) !== NoHookEffect
435 - ) {
436 - enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
437 - enqueuePendingPassiveHookEffectMount(finishedWork, effect);
438 - }
439 - effect = next;
440 - } while (effect !== firstEffect);
441 - }
442 -}
443 -
535 export function commitPassiveEffectDurations(
536 finishedRoot: FiberRoot,
537 finishedWork: Fiber,
@@ -527,8 +618,6 @@ function commitLayoutEffectOnFiber(
618 } else {
619 commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
620 }
530 -
531 - schedulePassiveEffects(finishedWork);
621 break;
622 }
623 case ClassComponent: {
@@ -979,9 +1068,7 @@ function commitUnmount(
1068 do {
1069 const {destroy, tag} = effect;
1070 if (destroy !== undefined) {
982 - if ((tag & HookPassive) !== NoHookEffect) {
983 - enqueuePendingPassiveHookEffectUnmount(current, effect);
984 - } else {
1071 + if ((tag & HookLayout) !== NoHookEffect) {
1072 if (
1073 enableProfilerTimer &&
1074 enableProfilerCommitHooks &&
@@ -1127,9 +1214,6 @@ export function detachFiberAfterEffects(fiber: Fiber): void {
1214 fiber.sibling = null;
1215 fiber.stateNode = null;
1216 fiber.updateQueue = null;
1130 - fiber.nextEffect = null;
1131 - fiber.firstEffect = null;
1132 - fiber.lastEffect = null;
1217
1218 if (__DEV__) {
1219 fiber._debugOwner = null;
@@ -2483,7 +2567,6 @@ function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
2567 }
2568
2569 export {
2486 - commitBeforeMutationLifeCycles,
2570 commitResetTextContent,
2571 commitPlacement,
2572 commitDeletion,
packages/react-reconciler/src/ReactFiberCompleteWork.old.js
+35 -22
@@ -72,7 +72,9 @@ import {
72 NoFlags,
73 DidCapture,
74 Snapshot,
75 + ChildDeletion,
76 StaticMask,
77 + MutationMask,
78 } from './ReactFiberFlags';
79 import invariant from 'shared/invariant';
80
@@ -173,6 +175,31 @@ function markRef(workInProgress: Fiber) {
175 workInProgress.flags |= Ref;
176 }
177
178 +function hadNoMutationsEffects(current: null | Fiber, completedWork: Fiber) {
179 + const didBailout = current !== null && current.child === completedWork.child;
180 + if (didBailout) {
181 + return true;
182 + }
183 +
184 + if ((completedWork.flags & ChildDeletion) !== NoFlags) {
185 + return false;
186 + }
187 +
188 + // TODO: If we move the `hadNoMutationsEffects` call after `bubbleProperties`
189 + // then we only have to check the `completedWork.subtreeFlags`.
190 + let child = completedWork.child;
191 + while (child !== null) {
192 + if (
193 + (child.flags & MutationMask) !== NoFlags ||
194 + (child.subtreeFlags & MutationMask) !== NoFlags
195 + ) {
196 + return false;
197 + }
198 + child = child.sibling;
199 + }
200 + return true;
201 +}
202 +
203 let appendAllChildren;
204 let updateHostContainer;
205 let updateHostComponent;
@@ -217,7 +244,7 @@ if (supportsMutation) {
244 }
245 };
246
220 - updateHostContainer = function(workInProgress: Fiber) {
247 + updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
248 // Noop
249 };
250 updateHostComponent = function(
@@ -461,13 +488,13 @@ if (supportsMutation) {
488 node = node.sibling;
489 }
490 };
464 - updateHostContainer = function(workInProgress: Fiber) {
491 + updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
492 const portalOrRoot: {
493 containerInfo: Container,
494 pendingChildren: ChildSet,
495 ...
496 } = workInProgress.stateNode;
470 - const childrenUnchanged = workInProgress.firstEffect === null;
497 + const childrenUnchanged = hadNoMutationsEffects(current, workInProgress);
498 if (childrenUnchanged) {
499 // No changes, just reuse the existing instance.
500 } else {
@@ -492,7 +519,7 @@ if (supportsMutation) {
519 const oldProps = current.memoizedProps;
520 // If there are no effects associated with this node, then none of our children had any updates.
521 // This guarantees that we can reuse all of them.
495 - const childrenUnchanged = workInProgress.firstEffect === null;
522 + const childrenUnchanged = hadNoMutationsEffects(current, workInProgress);
523 if (childrenUnchanged && oldProps === newProps) {
524 // No changes, just reuse the existing instance.
525 // Note that this might release a previous clone.
@@ -575,7 +602,7 @@ if (supportsMutation) {
602 };
603 } else {
604 // No host operations
578 - updateHostContainer = function(workInProgress: Fiber) {
605 + updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
606 // Noop
607 };
608 updateHostComponent = function(
@@ -847,7 +874,7 @@ function completeWork(
874 workInProgress.flags |= Snapshot;
875 }
876 }
850 - updateHostContainer(workInProgress);
877 + updateHostContainer(current, workInProgress);
878 bubbleProperties(workInProgress);
879 return null;
880 }
@@ -1142,7 +1169,7 @@ function completeWork(
1169 }
1170 case HostPortal:
1171 popHostContainer(workInProgress);
1145 - updateHostContainer(workInProgress);
1172 + updateHostContainer(current, workInProgress);
1173 if (current === null) {
1174 preparePortalMount(workInProgress.stateNode.containerInfo);
1175 }
@@ -1226,11 +1253,7 @@ function completeWork(
1253
1254 // Rerender the whole list, but this time, we'll force fallbacks
1255 // to stay in place.
1229 - // Reset the effect list before doing the second pass since that's now invalid.
1230 - if (renderState.lastEffect === null) {
1231 - workInProgress.firstEffect = null;
1232 - }
1233 - workInProgress.lastEffect = renderState.lastEffect;
1256 + // Reset the effect flags before doing the second pass since that's now invalid.
1257 // Reset the child fibers to their original state.
1258 workInProgress.subtreeFlags = NoFlags;
1259 resetChildFibers(workInProgress, renderLanes);
@@ -1301,15 +1324,6 @@ function completeWork(
1324 !renderedTail.alternate &&
1325 !getIsHydrating() // We don't cut it if we're hydrating.
1326 ) {
1304 - // We need to delete the row we just rendered.
1305 - // Reset the effect list to what it was before we rendered this
1306 - // child. The nested children have already appended themselves.
1307 - const lastEffect = (workInProgress.lastEffect =
1308 - renderState.lastEffect);
1309 - // Remove any effects that were appended after this point.
1310 - if (lastEffect !== null) {
1311 - lastEffect.nextEffect = null;
1312 - }
1327 // We're done.
1328 bubbleProperties(workInProgress);
1329 return null;
@@ -1369,7 +1383,6 @@ function completeWork(
1383 const next = renderState.tail;
1384 renderState.rendering = next;
1385 renderState.tail = next.sibling;
1372 - renderState.lastEffect = workInProgress.lastEffect;
1386 renderState.renderingStartTime = now();
1387 next.sibling = null;
1388
packages/react-reconciler/src/ReactFiberHooks.old.js
+6 -15
@@ -488,8 +488,8 @@ export function bailoutHooks(
488 (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
489 ) {
490 workInProgress.flags &= ~(
491 - MountLayoutDevEffect |
491 MountPassiveDevEffect |
492 + MountLayoutDevEffect |
493 PassiveEffect |
494 UpdateEffect
495 );
@@ -1337,17 +1337,14 @@ function mountEffect(
1337 (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1338 ) {
1339 return mountEffectImpl(
1340 - MountPassiveDevEffect |
1341 - UpdateEffect |
1342 - PassiveEffect |
1343 - PassiveStaticEffect,
1340 + MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
1341 HookPassive,
1342 create,
1343 deps,
1344 );
1345 } else {
1346 return mountEffectImpl(
1350 - UpdateEffect | PassiveEffect | PassiveStaticEffect,
1347 + PassiveEffect | PassiveStaticEffect,
1348 HookPassive,
1349 create,
1350 deps,
@@ -1365,12 +1362,7 @@ function updateEffect(
1362 warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
1363 }
1364 }
1368 - return updateEffectImpl(
1369 - UpdateEffect | PassiveEffect,
1370 - HookPassive,
1371 - create,
1372 - deps,
1373 - );
1365 + return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
1366 }
1367
1368 function mountLayoutEffect(
@@ -1749,10 +1741,9 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
1741
1742 if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
1743 if (__DEV__ && enableDoubleInvokingEffects) {
1752 - currentlyRenderingFiber.flags |=
1753 - MountPassiveDevEffect | UpdateEffect | PassiveEffect;
1744 + currentlyRenderingFiber.flags |= MountPassiveDevEffect | PassiveEffect;
1745 } else {
1755 - currentlyRenderingFiber.flags |= UpdateEffect | PassiveEffect;
1746 + currentlyRenderingFiber.flags |= PassiveEffect;
1747 }
1748 pushEffect(
1749 HookHasEffect | HookPassive,
packages/react-reconciler/src/ReactFiberHydrationContext.old.js
+1 -20
@@ -24,13 +24,7 @@ import {
24 HostRoot,
25 SuspenseComponent,
26 } from './ReactWorkTags';
27 -import {
28 - Deletion,
29 - ChildDeletion,
30 - Placement,
31 - Hydrating,
32 - StaticMask,
33 -} from './ReactFiberFlags';
27 +import {ChildDeletion, Placement, Hydrating} from './ReactFiberFlags';
28 import invariant from 'shared/invariant';
29
30 import {
@@ -130,19 +124,6 @@ function deleteHydratableInstance(
124 const childToDelete = createFiberFromHostInstanceForDeletion();
125 childToDelete.stateNode = instance;
126 childToDelete.return = returnFiber;
133 - childToDelete.flags = (childToDelete.flags & StaticMask) | Deletion;
134 -
135 - // This might seem like it belongs on progressedFirstDeletion. However,
136 - // these children are not part of the reconciliation list of children.
137 - // Even if we abort and rereconcile the children, that will try to hydrate
138 - // again and the nodes are still in the host tree so these will be
139 - // recreated.
140 - if (returnFiber.lastEffect !== null) {
141 - returnFiber.lastEffect.nextEffect = childToDelete;
142 - returnFiber.lastEffect = childToDelete;
143 - } else {
144 - returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
145 - }
127
128 const deletions = returnFiber.deletions;
129 if (deletions === null) {
packages/react-reconciler/src/ReactFiberSuspenseComponent.old.js
-3
@@ -60,9 +60,6 @@ export type SuspenseListRenderState = {|
60 tail: null | Fiber,
61 // Tail insertions setting.
62 tailMode: SuspenseListTailMode,
63 - // Last Effect before we rendered the "rendering" item.
64 - // Used to remove new effects added by the rendered item.
65 - lastEffect: null | Fiber,
63 |};
64
65 export function shouldCaptureSuspense(
packages/react-reconciler/src/ReactFiberThrow.old.js
-2
@@ -188,8 +188,6 @@ function throwException(
188 ) {
189 // The source fiber did not complete.
190 sourceFiber.flags |= Incomplete;
191 - // Its effect list is no longer valid.
192 - sourceFiber.firstEffect = sourceFiber.lastEffect = null;
191
192 if (
193 value !== null &&
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+40 -197
@@ -13,7 +13,6 @@ import type {Lanes, Lane} from './ReactFiberLane.old';
13 import type {ReactPriorityLevel} from './ReactInternalTypes';
14 import type {Interaction} from 'scheduler/src/Tracing';
15 import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
16 -import type {Effect as HookEffect} from './ReactFiberHooks.old';
16 import type {StackCursor} from './ReactFiberStack.old';
17 import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old';
18 import type {Flags} from './ReactFiberFlags';
@@ -85,13 +84,11 @@ import * as Scheduler from 'scheduler';
84 import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
85
86 import {
88 - prepareForCommit,
87 resetAfterCommit,
88 scheduleTimeout,
89 cancelTimeout,
90 noTimeout,
91 warnsIfNotActing,
94 - beforeActiveInstanceBlur,
92 afterActiveInstanceBlur,
93 clearContainer,
94 } from './ReactFiberHostConfig';
@@ -122,17 +119,15 @@ import {
119 import {LegacyRoot} from './ReactRootTags';
120 import {
121 NoFlags,
125 - PerformedWork,
122 Placement,
127 - Deletion,
128 - ChildDeletion,
129 - Snapshot,
130 - Passive,
123 PassiveStatic,
124 Incomplete,
125 HostEffectMask,
126 Hydrating,
135 - StaticMask,
127 + BeforeMutationMask,
128 + MutationMask,
129 + LayoutMask,
130 + PassiveMask,
131 MountPassiveDev,
132 MountLayoutDev,
133 } from './ReactFiberFlags';
@@ -185,14 +180,12 @@ import {
180 createClassErrorUpdate,
181 } from './ReactFiberThrow.old';
182 import {
188 - commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
183 + commitBeforeMutationEffects,
184 commitLayoutEffects,
185 commitMutationEffects,
186 commitPassiveEffectDurations,
192 - isSuspenseBoundaryBeingHidden,
187 commitPassiveMountEffects,
188 commitPassiveUnmountEffects,
195 - detachFiberAfterEffects,
189 invokeLayoutEffectMountInDEV,
190 invokePassiveEffectMountInDEV,
191 invokeLayoutEffectUnmountInDEV,
@@ -240,7 +233,6 @@ import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
233
234 // Used by `act`
235 import enqueueTask from 'shared/enqueueTask';
243 -import {doesFiberContain} from './ReactFiberTreeReflection';
236
237 const ceil = Math.ceil;
238
@@ -328,7 +320,6 @@ export function getRenderTargetTime(): number {
320 return workInProgressRootRenderTargetTime;
321 }
322
331 -let nextEffect: Fiber | null = null;
323 let hasUncaughtError = false;
324 let firstUncaughtError = null;
325 let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
@@ -371,9 +362,6 @@ let currentEventPendingLanes: Lanes = NoLanes;
362 // We warn about state updates for unmounted components differently in this case.
363 let isFlushingPassiveEffects = false;
364
374 -let focusedInstanceHandle: null | Fiber = null;
375 -let shouldFireAfterActiveInstanceBlur: boolean = false;
376 -
365 export function getWorkInProgressRoot(): FiberRoot | null {
366 return workInProgressRoot;
367 }
@@ -1737,45 +1725,6 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1725 workInProgress = next;
1726 return;
1727 }
1740 -
1741 - if (
1742 - returnFiber !== null &&
1743 - // Do not append effects to parents if a sibling failed to complete
1744 - (returnFiber.flags & Incomplete) === NoFlags
1745 - ) {
1746 - // Append all the effects of the subtree and this fiber onto the effect
1747 - // list of the parent. The completion order of the children affects the
1748 - // side-effect order.
1749 - if (returnFiber.firstEffect === null) {
1750 - returnFiber.firstEffect = completedWork.firstEffect;
1751 - }
1752 - if (completedWork.lastEffect !== null) {
1753 - if (returnFiber.lastEffect !== null) {
1754 - returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
1755 - }
1756 - returnFiber.lastEffect = completedWork.lastEffect;
1757 - }
1758 -
1759 - // If this fiber had side-effects, we append it AFTER the children's
1760 - // side-effects. We can perform certain side-effects earlier if needed,
1761 - // by doing multiple passes over the effect list. We don't want to
1762 - // schedule our own side-effect on our own list because if end up
1763 - // reusing children we'll schedule this effect onto itself since we're
1764 - // at the end.
1765 - const flags = completedWork.flags;
1766 -
1767 - // Skip both NoWork and PerformedWork tags when creating the effect
1768 - // list. PerformedWork effect is read by React DevTools but shouldn't be
1769 - // committed.
1770 - if ((flags & ~StaticMask) > PerformedWork) {
1771 - if (returnFiber.lastEffect !== null) {
1772 - returnFiber.lastEffect.nextEffect = completedWork;
1773 - } else {
1774 - returnFiber.firstEffect = completedWork;
1775 - }
1776 - returnFiber.lastEffect = completedWork;
1777 - }
1778 - }
1728 } else {
1729 // This fiber did not complete because something threw. Pop values off
1730 // the stack without entering the complete phase. If this is a boundary,
@@ -1812,8 +1761,7 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1761 }
1762
1763 if (returnFiber !== null) {
1815 - // Mark the parent fiber as incomplete and clear its effect list.
1816 - returnFiber.firstEffect = returnFiber.lastEffect = null;
1764 + // Mark the parent fiber as incomplete and clear its subtree flags.
1765 returnFiber.flags |= Incomplete;
1766 returnFiber.subtreeFlags = NoFlags;
1767 returnFiber.deletions = null;
@@ -1931,25 +1879,39 @@ function commitRootImpl(root, renderPriorityLevel) {
1879 // times out.
1880 }
1881
1934 - // Get the list of effects.
1935 - let firstEffect;
1936 - if (finishedWork.flags > PerformedWork) {
1937 - // A fiber's effect list consists only of its children, not itself. So if
1938 - // the root has an effect, we need to add it to the end of the list. The
1939 - // resulting list is the set that would belong to the root's parent, if it
1940 - // had one; that is, all the effects in the tree including the root.
1941 - if (finishedWork.lastEffect !== null) {
1942 - finishedWork.lastEffect.nextEffect = finishedWork;
1943 - firstEffect = finishedWork.firstEffect;
1944 - } else {
1945 - firstEffect = finishedWork;
1882 + // If there are pending passive effects, schedule a callback to process them.
1883 + // Do this as early as possible, so it is queued before anything else that
1884 + // might get scheduled in the commit phase. (See #16714.)
1885 + // TODO: Delete all other places that schedule the passive effect callback
1886 + // They're redundant.
1887 + if (
1888 + (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
1889 + (finishedWork.flags & PassiveMask) !== NoFlags
1890 + ) {
1891 + if (!rootDoesHavePassiveEffects) {
1892 + rootDoesHavePassiveEffects = true;
1893 + scheduleCallback(NormalSchedulerPriority, () => {
1894 + flushPassiveEffects();
1895 + return null;
1896 + });
1897 }
1947 - } else {
1948 - // There is no effect on the root.
1949 - firstEffect = finishedWork.firstEffect;
1898 }
1899
1952 - if (firstEffect !== null) {
1900 + // Check if there are any effects in the whole tree.
1901 + // TODO: This is left over from the effect list implementation, where we had
1902 + // to check for the existence of `firstEffect` to satsify Flow. I think the
1903 + // only other reason this optimization exists is because it affects profiling.
1904 + // Reconsider whether this is necessary.
1905 + const subtreeHasEffects =
1906 + (finishedWork.subtreeFlags &
1907 + (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
1908 + NoFlags;
1909 + const rootHasEffect =
1910 + (finishedWork.flags &
1911 + (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
1912 + NoFlags;
1913 +
1914 + if (subtreeHasEffects || rootHasEffect) {
1915 let previousLanePriority;
1916 if (decoupleUpdatePriorityFromScheduler) {
1917 previousLanePriority = getCurrentUpdateLanePriority();
@@ -1970,32 +1932,10 @@ function commitRootImpl(root, renderPriorityLevel) {
1932 // The first phase a "before mutation" phase. We use this phase to read the
1933 // state of the host tree right before we mutate it. This is where
1934 // getSnapshotBeforeUpdate is called.
1973 - focusedInstanceHandle = prepareForCommit(root.containerInfo);
1974 - shouldFireAfterActiveInstanceBlur = false;
1975 -
1976 - nextEffect = firstEffect;
1977 - do {
1978 - if (__DEV__) {
1979 - invokeGuardedCallback(null, commitBeforeMutationEffects, null);
1980 - if (hasCaughtError()) {
1981 - invariant(nextEffect !== null, 'Should be working on an effect.');
1982 - const error = clearCaughtError();
1983 - captureCommitPhaseError(nextEffect, error);
1984 - nextEffect = nextEffect.nextEffect;
1985 - }
1986 - } else {
1987 - try {
1988 - commitBeforeMutationEffects();
1989 - } catch (error) {
1990 - invariant(nextEffect !== null, 'Should be working on an effect.');
1991 - captureCommitPhaseError(nextEffect, error);
1992 - nextEffect = nextEffect.nextEffect;
1993 - }
1994 - }
1995 - } while (nextEffect !== null);
1996 -
1997 - // We no longer need to track the active instance fiber
1998 - focusedInstanceHandle = null;
1935 + const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
1936 + root,
1937 + finishedWork,
1938 + );
1939
1940 if (enableProfilerTimer) {
1941 // Mark the current commit time to be shared by all Profilers in this
@@ -2082,31 +2022,6 @@ function commitRootImpl(root, renderPriorityLevel) {
2022 rootWithPendingPassiveEffects = root;
2023 pendingPassiveEffectsLanes = lanes;
2024 pendingPassiveEffectsRenderPriority = renderPriorityLevel;
2085 - } else {
2086 - // We are done with the effect chain at this point so let's clear the
2087 - // nextEffect pointers to assist with GC. If we have passive effects, we'll
2088 - // clear this in flushPassiveEffects
2089 - // TODO: We should always do this in the passive phase, by scheduling
2090 - // a passive callback for every deletion.
2091 - nextEffect = firstEffect;
2092 - while (nextEffect !== null) {
2093 - const nextNextEffect = nextEffect.nextEffect;
2094 - nextEffect.nextEffect = null;
2095 - if (nextEffect.flags & ChildDeletion) {
2096 - const deletions = nextEffect.deletions;
2097 - if (deletions !== null) {
2098 - for (let i = 0; i < deletions.length; i++) {
2099 - const deletion = deletions[i];
2100 - const alternate = deletion.alternate;
2101 - detachFiberAfterEffects(deletion);
2102 - if (alternate !== null) {
2103 - detachFiberAfterEffects(alternate);
2104 - }
2105 - }
2106 - }
2107 - }
2108 - nextEffect = nextNextEffect;
2109 - }
2025 }
2026
2027 // Read this again, since an effect might have updated it
@@ -2218,52 +2133,6 @@ function commitRootImpl(root, renderPriorityLevel) {
2133 return null;
2134 }
2135
2221 -function commitBeforeMutationEffects() {
2222 - while (nextEffect !== null) {
2223 - const current = nextEffect.alternate;
2224 -
2225 - if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
2226 - if ((nextEffect.flags & Deletion) !== NoFlags) {
2227 - if (doesFiberContain(nextEffect, focusedInstanceHandle)) {
2228 - shouldFireAfterActiveInstanceBlur = true;
2229 - beforeActiveInstanceBlur(nextEffect);
2230 - }
2231 - } else {
2232 - // TODO: Move this out of the hot path using a dedicated effect tag.
2233 - if (
2234 - nextEffect.tag === SuspenseComponent &&
2235 - isSuspenseBoundaryBeingHidden(current, nextEffect) &&
2236 - doesFiberContain(nextEffect, focusedInstanceHandle)
2237 - ) {
2238 - shouldFireAfterActiveInstanceBlur = true;
2239 - beforeActiveInstanceBlur(nextEffect);
2240 - }
2241 - }
2242 - }
2243 -
2244 - const flags = nextEffect.flags;
2245 - if ((flags & Snapshot) !== NoFlags) {
2246 - setCurrentDebugFiberInDEV(nextEffect);
2247 -
2248 - commitBeforeMutationEffectOnFiber(current, nextEffect);
2249 -
2250 - resetCurrentDebugFiberInDEV();
2251 - }
2252 - if ((flags & Passive) !== NoFlags) {
2253 - // If there are passive effects, schedule a callback to flush at
2254 - // the earliest opportunity.
2255 - if (!rootDoesHavePassiveEffects) {
2256 - rootDoesHavePassiveEffects = true;
2257 - scheduleCallback(NormalSchedulerPriority, () => {
2258 - flushPassiveEffects();
2259 - return null;
2260 - });
2261 - }
2262 - }
2263 - nextEffect = nextEffect.nextEffect;
2264 - }
2265 -}
2266 -
2136 export function flushPassiveEffects(): boolean {
2137 // Returns whether passive effects were flushed.
2138 if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
@@ -2302,32 +2171,6 @@ export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void {
2171 }
2172 }
2173
2305 -export function enqueuePendingPassiveHookEffectMount(
2306 - fiber: Fiber,
2307 - effect: HookEffect,
2308 -): void {
2309 - if (!rootDoesHavePassiveEffects) {
2310 - rootDoesHavePassiveEffects = true;
2311 - scheduleCallback(NormalSchedulerPriority, () => {
2312 - flushPassiveEffects();
2313 - return null;
2314 - });
2315 - }
2316 -}
2317 -
2318 -export function enqueuePendingPassiveHookEffectUnmount(
2319 - fiber: Fiber,
2320 - effect: HookEffect,
2321 -): void {
2322 - if (!rootDoesHavePassiveEffects) {
2323 - rootDoesHavePassiveEffects = true;
2324 - scheduleCallback(NormalSchedulerPriority, () => {
2325 - flushPassiveEffects();
2326 - return null;
2327 - });
2328 - }
2329 -}
2330 -
2174 function flushPassiveEffectsImpl() {
2175 if (rootWithPendingPassiveEffects === null) {
2176 return false;