414
workInProgress: Fiber,
415
Component: any,
416
nextProps: any,
417
- updateLanes: Lanes,
417
renderLanes: Lanes,
418
): null | Fiber {
419
if (current === null) {
441
workInProgress,
442
resolvedType,
443
nextProps,
445
- updateLanes,
444
renderLanes,
445
);
446
}
485
}
486
}
487
const currentChild = ((current.child: any): Fiber); // This is always exactly one child
490
- if (!includesSomeLane(updateLanes, renderLanes)) {
488
+ const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
489
+ current,
490
+ renderLanes,
491
+ );
492
+ if (!hasScheduledUpdateOrContext) {
493
// This will be the props with resolved defaultProps,
494
// unlike current.memoizedProps which will be the unresolved ones.
495
const prevProps = currentChild.memoizedProps;
514
workInProgress: Fiber,
515
Component: any,
516
nextProps: any,
515
- updateLanes: Lanes,
517
renderLanes: Lanes,
518
): null | Fiber {
519
// TODO: current can be non-null here even if the component
559
(__DEV__ ? workInProgress.type === current.type : true)
560
) {
561
didReceiveUpdate = false;
561
- if (!includesSomeLane(renderLanes, updateLanes)) {
562
+ if (!checkScheduledUpdateOrContext(current, renderLanes)) {
563
// The pending lanes were cleared at the beginning of beginWork. We're
564
// about to bail out, but there might be other lanes that weren't
565
// included in the current render. Usually, the priority level of the
807
function updateCacheComponent(
808
current: Fiber | null,
809
workInProgress: Fiber,
809
- updateLanes: Lanes,
810
renderLanes: Lanes,
811
) {
812
if (!enableCache) {
828
pushCacheProvider(workInProgress, freshCache);
829
} else {
830
// Check for updates
831
- if (includesSomeLane(renderLanes, updateLanes)) {
831
+ if (includesSomeLane(current.lanes, renderLanes)) {
832
cloneUpdateQueue(current, workInProgress);
833
processUpdateQueue(workInProgress, null, null, renderLanes);
834
}
1372
_current,
1373
workInProgress,
1374
elementType,
1375
- updateLanes,
1375
renderLanes,
1376
) {
1377
if (_current !== null) {
1461
workInProgress,
1462
Component,
1463
resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too
1465
- updateLanes,
1464
renderLanes,
1465
);
1466
return child;
3345
}
3346
}
3347
3348
+function checkScheduledUpdateOrContext(
3349
+ current: Fiber,
3350
+ renderLanes: Lanes,
3351
+): boolean {
3352
+ // Before performing an early bailout, we must check if there are pending
3353
+ // updates or context.
3354
+ const updateLanes = current.lanes;
3355
+ if (includesSomeLane(updateLanes, renderLanes)) {
3356
+ return true;
3357
+ }
3358
+ // No pending update, but because context is propagated lazily, we need
3359
+ // to check for a context change before we bail out.
3360
+ if (enableLazyContextPropagation) {
3361
+ const dependencies = current.dependencies;
3362
+ if (dependencies !== null && checkIfContextChanged(dependencies)) {
3363
+ return true;
3364
+ }
3365
+ }
3366
+ return false;
3367
+}
3368
+
3369
+function attemptEarlyBailoutIfNoScheduledUpdate(
3370
+ current: Fiber,
3371
+ workInProgress: Fiber,
3372
+ renderLanes: Lanes,
3373
+) {
3374
+ // This fiber does not have any pending work. Bailout without entering
3375
+ // the begin phase. There's still some bookkeeping we that needs to be done
3376
+ // in this optimized path, mostly pushing stuff onto the stack.
3377
+ switch (workInProgress.tag) {
3378
+ case HostRoot:
3379
+ pushHostRootContext(workInProgress);
3380
+ if (enableCache) {
3381
+ const root: FiberRoot = workInProgress.stateNode;
3382
+ const cache: Cache = current.memoizedState.cache;
3383
+ pushCacheProvider(workInProgress, cache);
3384
+ pushRootCachePool(root);
3385
+ }
3386
+ resetHydrationState();
3387
+ break;
3388
+ case HostComponent:
3389
+ pushHostContext(workInProgress);
3390
+ break;
3391
+ case ClassComponent: {
3392
+ const Component = workInProgress.type;
3393
+ if (isLegacyContextProvider(Component)) {
3394
+ pushLegacyContextProvider(workInProgress);
3395
+ }
3396
+ break;
3397
+ }
3398
+ case HostPortal:
3399
+ pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
3400
+ break;
3401
+ case ContextProvider: {
3402
+ const newValue = workInProgress.memoizedProps.value;
3403
+ const context: ReactContext<any> = workInProgress.type._context;
3404
+ pushProvider(workInProgress, context, newValue);
3405
+ break;
3406
+ }
3407
+ case Profiler:
3408
+ if (enableProfilerTimer) {
3409
+ // Profiler should only call onRender when one of its descendants actually rendered.
3410
+ const hasChildWork = includesSomeLane(
3411
+ renderLanes,
3412
+ workInProgress.childLanes,
3413
+ );
3414
+ if (hasChildWork) {
3415
+ workInProgress.flags |= Update;
3416
+ }
3417
+
3418
+ if (enableProfilerCommitHooks) {
3419
+ // Reset effect durations for the next eventual effect phase.
3420
+ // These are reset during render to allow the DevTools commit hook a chance to read them,
3421
+ const stateNode = workInProgress.stateNode;
3422
+ stateNode.effectDuration = 0;
3423
+ stateNode.passiveEffectDuration = 0;
3424
+ }
3425
+ }
3426
+ break;
3427
+ case SuspenseComponent: {
3428
+ const state: SuspenseState | null = workInProgress.memoizedState;
3429
+ if (state !== null) {
3430
+ if (enableSuspenseServerRenderer) {
3431
+ if (state.dehydrated !== null) {
3432
+ pushSuspenseContext(
3433
+ workInProgress,
3434
+ setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3435
+ );
3436
+ // We know that this component will suspend again because if it has
3437
+ // been unsuspended it has committed as a resolved Suspense component.
3438
+ // If it needs to be retried, it should have work scheduled on it.
3439
+ workInProgress.flags |= DidCapture;
3440
+ // We should never render the children of a dehydrated boundary until we
3441
+ // upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
3442
+ return null;
3443
+ }
3444
+ }
3445
+
3446
+ // If this boundary is currently timed out, we need to decide
3447
+ // whether to retry the primary children, or to skip over it and
3448
+ // go straight to the fallback. Check the priority of the primary
3449
+ // child fragment.
3450
+ const primaryChildFragment: Fiber = (workInProgress.child: any);
3451
+ const primaryChildLanes = primaryChildFragment.childLanes;
3452
+ if (includesSomeLane(renderLanes, primaryChildLanes)) {
3453
+ // The primary children have pending work. Use the normal path
3454
+ // to attempt to render the primary children again.
3455
+ return updateSuspenseComponent(current, workInProgress, renderLanes);
3456
+ } else {
3457
+ // The primary child fragment does not have pending work marked
3458
+ // on it
3459
+ pushSuspenseContext(
3460
+ workInProgress,
3461
+ setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3462
+ );
3463
+ // The primary children do not have pending work with sufficient
3464
+ // priority. Bailout.
3465
+ const child = bailoutOnAlreadyFinishedWork(
3466
+ current,
3467
+ workInProgress,
3468
+ renderLanes,
3469
+ );
3470
+ if (child !== null) {
3471
+ // The fallback children have pending work. Skip over the
3472
+ // primary children and work on the fallback.
3473
+ return child.sibling;
3474
+ } else {
3475
+ // Note: We can return `null` here because we already checked
3476
+ // whether there were nested context consumers, via the call to
3477
+ // `bailoutOnAlreadyFinishedWork` above.
3478
+ return null;
3479
+ }
3480
+ }
3481
+ } else {
3482
+ pushSuspenseContext(
3483
+ workInProgress,
3484
+ setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3485
+ );
3486
+ }
3487
+ break;
3488
+ }
3489
+ case SuspenseListComponent: {
3490
+ const didSuspendBefore = (current.flags & DidCapture) !== NoFlags;
3491
+
3492
+ let hasChildWork = includesSomeLane(
3493
+ renderLanes,
3494
+ workInProgress.childLanes,
3495
+ );
3496
+
3497
+ if (enableLazyContextPropagation && !hasChildWork) {
3498
+ // Context changes may not have been propagated yet. We need to do
3499
+ // that now, before we can decide whether to bail out.
3500
+ // TODO: We use `childLanes` as a heuristic for whether there is
3501
+ // remaining work in a few places, including
3502
+ // `bailoutOnAlreadyFinishedWork` and
3503
+ // `updateDehydratedSuspenseComponent`. We should maybe extract this
3504
+ // into a dedicated function.
3505
+ lazilyPropagateParentContextChanges(
3506
+ current,
3507
+ workInProgress,
3508
+ renderLanes,
3509
+ );
3510
+ hasChildWork = includesSomeLane(renderLanes, workInProgress.childLanes);
3511
+ }
3512
+
3513
+ if (didSuspendBefore) {
3514
+ if (hasChildWork) {
3515
+ // If something was in fallback state last time, and we have all the
3516
+ // same children then we're still in progressive loading state.
3517
+ // Something might get unblocked by state updates or retries in the
3518
+ // tree which will affect the tail. So we need to use the normal
3519
+ // path to compute the correct tail.
3520
+ return updateSuspenseListComponent(
3521
+ current,
3522
+ workInProgress,
3523
+ renderLanes,
3524
+ );
3525
+ }
3526
+ // If none of the children had any work, that means that none of
3527
+ // them got retried so they'll still be blocked in the same way
3528
+ // as before. We can fast bail out.
3529
+ workInProgress.flags |= DidCapture;
3530
+ }
3531
+
3532
+ // If nothing suspended before and we're rendering the same children,
3533
+ // then the tail doesn't matter. Anything new that suspends will work
3534
+ // in the "together" mode, so we can continue from the state we had.
3535
+ const renderState = workInProgress.memoizedState;
3536
+ if (renderState !== null) {
3537
+ // Reset to the "together" mode in case we've started a different
3538
+ // update in the past but didn't complete it.
3539
+ renderState.rendering = null;
3540
+ renderState.tail = null;
3541
+ renderState.lastEffect = null;
3542
+ }
3543
+ pushSuspenseContext(workInProgress, suspenseStackCursor.current);
3544
+
3545
+ if (hasChildWork) {
3546
+ break;
3547
+ } else {
3548
+ // If none of the children had any work, that means that none of
3549
+ // them got retried so they'll still be blocked in the same way
3550
+ // as before. We can fast bail out.
3551
+ return null;
3552
+ }
3553
+ }
3554
+ case OffscreenComponent:
3555
+ case LegacyHiddenComponent: {
3556
+ // Need to check if the tree still needs to be deferred. This is
3557
+ // almost identical to the logic used in the normal update path,
3558
+ // so we'll just enter that. The only difference is we'll bail out
3559
+ // at the next level instead of this one, because the child props
3560
+ // have not changed. Which is fine.
3561
+ // TODO: Probably should refactor `beginWork` to split the bailout
3562
+ // path from the normal path. I'm tempted to do a labeled break here
3563
+ // but I won't :)
3564
+ workInProgress.lanes = NoLanes;
3565
+ return updateOffscreenComponent(current, workInProgress, renderLanes);
3566
+ }
3567
+ case CacheComponent: {
3568
+ if (enableCache) {
3569
+ const cache: Cache = current.memoizedState.cache;
3570
+ pushCacheProvider(workInProgress, cache);
3571
+ }
3572
+ break;
3573
+ }
3574
+ }
3575
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
3576
+}
3577
+
3578
function beginWork(
3579
current: Fiber | null,
3580
workInProgress: Fiber,
3581
renderLanes: Lanes,
3582
): Fiber | null {
3355
- let updateLanes = workInProgress.lanes;
3356
-
3583
if (__DEV__) {
3584
if (workInProgress._debugNeedsRemount && current !== null) {
3585
// This will restart the begin phase with a new fiber.
3599
}
3600
3601
if (current !== null) {
3376
- // TODO: The factoring of this block is weird.
3377
- if (
3378
- enableLazyContextPropagation &&
3379
- !includesSomeLane(renderLanes, updateLanes)
3380
- ) {
3381
- const dependencies = current.dependencies;
3382
- if (dependencies !== null && checkIfContextChanged(dependencies)) {
3383
- updateLanes = mergeLanes(updateLanes, renderLanes);
3384
- }
3385
- }
3386
-
3602
const oldProps = current.memoizedProps;
3603
const newProps = workInProgress.pendingProps;
3604
3611
// If props or context changed, mark the fiber as having performed work.
3612
// This may be unset if the props are determined to be equal later (memo).
3613
didReceiveUpdate = true;
3399
- } else if (!includesSomeLane(renderLanes, updateLanes)) {
3400
- didReceiveUpdate = false;
3401
- // This fiber does not have any pending work. Bailout without entering
3402
- // the begin phase. There's still some bookkeeping we that needs to be done
3403
- // in this optimized path, mostly pushing stuff onto the stack.
3404
- switch (workInProgress.tag) {
3405
- case HostRoot:
3406
- pushHostRootContext(workInProgress);
3407
- if (enableCache) {
3408
- const root: FiberRoot = workInProgress.stateNode;
3409
- const cache: Cache = current.memoizedState.cache;
3410
- pushCacheProvider(workInProgress, cache);
3411
- pushRootCachePool(root);
3412
- }
3413
- resetHydrationState();
3414
- break;
3415
- case HostComponent:
3416
- pushHostContext(workInProgress);
3417
- break;
3418
- case ClassComponent: {
3419
- const Component = workInProgress.type;
3420
- if (isLegacyContextProvider(Component)) {
3421
- pushLegacyContextProvider(workInProgress);
3422
- }
3423
- break;
3424
- }
3425
- case HostPortal:
3426
- pushHostContainer(
3427
- workInProgress,
3428
- workInProgress.stateNode.containerInfo,
3429
- );
3430
- break;
3431
- case ContextProvider: {
3432
- const newValue = workInProgress.memoizedProps.value;
3433
- const context: ReactContext<any> = workInProgress.type._context;
3434
- pushProvider(workInProgress, context, newValue);
3435
- break;
3436
- }
3437
- case Profiler:
3438
- if (enableProfilerTimer) {
3439
- // Profiler should only call onRender when one of its descendants actually rendered.
3440
- const hasChildWork = includesSomeLane(
3441
- renderLanes,
3442
- workInProgress.childLanes,
3443
- );
3444
- if (hasChildWork) {
3445
- workInProgress.flags |= Update;
3446
- }
3447
-
3448
- if (enableProfilerCommitHooks) {
3449
- // Reset effect durations for the next eventual effect phase.
3450
- // These are reset during render to allow the DevTools commit hook a chance to read them,
3451
- const stateNode = workInProgress.stateNode;
3452
- stateNode.effectDuration = 0;
3453
- stateNode.passiveEffectDuration = 0;
3454
- }
3455
- }
3456
- break;
3457
- case SuspenseComponent: {
3458
- const state: SuspenseState | null = workInProgress.memoizedState;
3459
- if (state !== null) {
3460
- if (enableSuspenseServerRenderer) {
3461
- if (state.dehydrated !== null) {
3462
- pushSuspenseContext(
3463
- workInProgress,
3464
- setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3465
- );
3466
- // We know that this component will suspend again because if it has
3467
- // been unsuspended it has committed as a resolved Suspense component.
3468
- // If it needs to be retried, it should have work scheduled on it.
3469
- workInProgress.flags |= DidCapture;
3470
- // We should never render the children of a dehydrated boundary until we
3471
- // upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
3472
- return null;
3473
- }
3474
- }
3475
-
3476
- // If this boundary is currently timed out, we need to decide
3477
- // whether to retry the primary children, or to skip over it and
3478
- // go straight to the fallback. Check the priority of the primary
3479
- // child fragment.
3480
- const primaryChildFragment: Fiber = (workInProgress.child: any);
3481
- const primaryChildLanes = primaryChildFragment.childLanes;
3482
- if (includesSomeLane(renderLanes, primaryChildLanes)) {
3483
- // The primary children have pending work. Use the normal path
3484
- // to attempt to render the primary children again.
3485
- return updateSuspenseComponent(
3486
- current,
3487
- workInProgress,
3488
- renderLanes,
3489
- );
3490
- } else {
3491
- // The primary child fragment does not have pending work marked
3492
- // on it
3493
- pushSuspenseContext(
3494
- workInProgress,
3495
- setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3496
- );
3497
- // The primary children do not have pending work with sufficient
3498
- // priority. Bailout.
3499
- const child = bailoutOnAlreadyFinishedWork(
3500
- current,
3501
- workInProgress,
3502
- renderLanes,
3503
- );
3504
- if (child !== null) {
3505
- // The fallback children have pending work. Skip over the
3506
- // primary children and work on the fallback.
3507
- return child.sibling;
3508
- } else {
3509
- // Note: We can return `null` here because we already checked
3510
- // whether there were nested context consumers, via the call to
3511
- // `bailoutOnAlreadyFinishedWork` above.
3512
- return null;
3513
- }
3514
- }
3515
- } else {
3516
- pushSuspenseContext(
3517
- workInProgress,
3518
- setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3519
- );
3520
- }
3521
- break;
3522
- }
3523
- case SuspenseListComponent: {
3524
- const didSuspendBefore = (current.flags & DidCapture) !== NoFlags;
3525
-
3526
- let hasChildWork = includesSomeLane(
3527
- renderLanes,
3528
- workInProgress.childLanes,
3529
- );
3530
-
3531
- if (enableLazyContextPropagation && !hasChildWork) {
3532
- // Context changes may not have been propagated yet. We need to do
3533
- // that now, before we can decide whether to bail out.
3534
- // TODO: We use `childLanes` as a heuristic for whether there is
3535
- // remaining work in a few places, including
3536
- // `bailoutOnAlreadyFinishedWork` and
3537
- // `updateDehydratedSuspenseComponent`. We should maybe extract this
3538
- // into a dedicated function.
3539
- lazilyPropagateParentContextChanges(
3540
- current,
3541
- workInProgress,
3542
- renderLanes,
3543
- );
3544
- hasChildWork = includesSomeLane(
3545
- renderLanes,
3546
- workInProgress.childLanes,
3547
- );
3548
- }
3549
-
3550
- if (didSuspendBefore) {
3551
- if (hasChildWork) {
3552
- // If something was in fallback state last time, and we have all the
3553
- // same children then we're still in progressive loading state.
3554
- // Something might get unblocked by state updates or retries in the
3555
- // tree which will affect the tail. So we need to use the normal
3556
- // path to compute the correct tail.
3557
- return updateSuspenseListComponent(
3558
- current,
3559
- workInProgress,
3560
- renderLanes,
3561
- );
3562
- }
3563
- // If none of the children had any work, that means that none of
3564
- // them got retried so they'll still be blocked in the same way
3565
- // as before. We can fast bail out.
3566
- workInProgress.flags |= DidCapture;
3567
- }
3568
-
3569
- // If nothing suspended before and we're rendering the same children,
3570
- // then the tail doesn't matter. Anything new that suspends will work
3571
- // in the "together" mode, so we can continue from the state we had.
3572
- const renderState = workInProgress.memoizedState;
3573
- if (renderState !== null) {
3574
- // Reset to the "together" mode in case we've started a different
3575
- // update in the past but didn't complete it.
3576
- renderState.rendering = null;
3577
- renderState.tail = null;
3578
- renderState.lastEffect = null;
3579
- }
3580
- pushSuspenseContext(workInProgress, suspenseStackCursor.current);
3581
-
3582
- if (hasChildWork) {
3583
- break;
3584
- } else {
3585
- // If none of the children had any work, that means that none of
3586
- // them got retried so they'll still be blocked in the same way
3587
- // as before. We can fast bail out.
3588
- return null;
3589
- }
3590
- }
3591
- case OffscreenComponent:
3592
- case LegacyHiddenComponent: {
3593
- // Need to check if the tree still needs to be deferred. This is
3594
- // almost identical to the logic used in the normal update path,
3595
- // so we'll just enter that. The only difference is we'll bail out
3596
- // at the next level instead of this one, because the child props
3597
- // have not changed. Which is fine.
3598
- // TODO: Probably should refactor `beginWork` to split the bailout
3599
- // path from the normal path. I'm tempted to do a labeled break here
3600
- // but I won't :)
3601
- workInProgress.lanes = NoLanes;
3602
- return updateOffscreenComponent(current, workInProgress, renderLanes);
3603
- }
3604
- case CacheComponent: {
3605
- if (enableCache) {
3606
- const cache: Cache = current.memoizedState.cache;
3607
- pushCacheProvider(workInProgress, cache);
3608
- }
3609
- break;
3610
- }
3611
- }
3612
- return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
3614
} else {
3615
+ // Neither props nor legacy context changes. Check if there's a pending
3616
+ // update or context change.
3617
+ const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
3618
+ current,
3619
+ renderLanes,
3620
+ );
3621
+ if (
3622
+ !hasScheduledUpdateOrContext &&
3623
+ // If this is the second pass of an error or suspense boundary, there
3624
+ // may not be work scheduled on `current`, so we check for this flag.
3625
+ (workInProgress.flags & DidCapture) === NoFlags
3626
+ ) {
3627
+ // No pending updates or context. Bail out now.
3628
+ didReceiveUpdate = false;
3629
+ return attemptEarlyBailoutIfNoScheduledUpdate(
3630
+ current,
3631
+ workInProgress,
3632
+ renderLanes,
3633
+ );
3634
+ }
3635
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
3636
// This is a special case that only exists for legacy mode.
3637
// See https://github.com/facebook/react/pull/19216.
3670
current,
3671
workInProgress,
3672
elementType,
3652
- updateLanes,
3673
renderLanes,
3674
);
3675
}
3762
workInProgress,
3763
type,
3764
resolvedProps,
3745
- updateLanes,
3765
renderLanes,
3766
);
3767
}
3771
workInProgress,
3772
workInProgress.type,
3773
workInProgress.pendingProps,
3755
- updateLanes,
3774
renderLanes,
3775
);
3776
}
3806
}
3807
case CacheComponent: {
3808
if (enableCache) {
3791
- return updateCacheComponent(
3792
- current,
3793
- workInProgress,
3794
- updateLanes,
3795
- renderLanes,
3796
- );
3809
+ return updateCacheComponent(current, workInProgress, renderLanes);
3810
}
3811
break;
3812
}