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;