@@ -197,10 +197,15 @@ export function propagateContextChange<T>(
197
197
renderLanes: Lanes,
198
198
): void {
199
199
if ( enableLazyContextPropagation ) {
200
+ // TODO: This path is only used by Cache components. Update
201
+ // lazilyPropagateParentContextChanges to look for Cache components so they
202
+ // can take advantage of lazy propagation.
203
+ const forcePropagateEntireTree = true ;
200
204
propagateContextChanges (
201
205
workInProgress ,
202
206
[ context , changedBits ] ,
203
207
renderLanes ,
208
+ forcePropagateEntireTree ,
204
209
) ;
205
210
} else {
206
211
propagateContextChange_eager (
@@ -349,6 +354,7 @@ function propagateContextChanges<T>(
349
354
workInProgress: Fiber,
350
355
contexts: Array< any > ,
351
356
renderLanes: Lanes,
357
+ forcePropagateEntireTree: boolean,
352
358
): void {
353
359
// Only used by lazy implemenation
354
360
if ( ! enableLazyContextPropagation ) {
@@ -397,6 +403,22 @@ function propagateContextChanges<T>(
397
403
}
398
404
scheduleWorkOnParentPath ( consumer . return , renderLanes ) ;
399
405
406
+ if ( ! forcePropagateEntireTree ) {
407
+ // During lazy propagation, when we find a match, we can defer
408
+ // propagating changes to the children, because we're going to
409
+ // visit them during render. We should continue propagating the
410
+ // siblings, though
411
+ nextFiber = null ;
412
+
413
+ // Keep track of subtrees whose propagation we deferred
414
+ if ( deferredPropagation === null ) {
415
+ deferredPropagation = new Set ( [ consumer ] ) ;
416
+ } else {
417
+ deferredPropagation . add ( consumer ) ;
418
+ }
419
+ nextFiber = null ;
420
+ }
421
+
400
422
// Since we already found a match, we can stop traversing the
401
423
// dependency list.
402
424
break findChangedDep ;
@@ -429,7 +451,7 @@ function propagateContextChanges<T>(
429
451
// on its children. We'll use the childLanes on
430
452
// this fiber to indicate that a context has changed.
431
453
scheduleWorkOnParentPath(parentSuspense, renderLanes);
432
- nextFiber = fiber.sibling ;
454
+ nextFiber = null ;
433
455
} else {
434
456
// Traverse down.
435
457
nextFiber = fiber . child ;
@@ -462,14 +484,58 @@ function propagateContextChanges<T>(
462
484
}
463
485
}
464
486
465
- // Alias for propagating a deferred tree (Suspense, Offscreen). Currently it's
466
- // the same algorithm but there may be a way to optimize one or the other.
467
- export const propagateParentContextChangesToDeferredTree = lazilyPropagateParentContextChanges ;
468
-
469
487
export function lazilyPropagateParentContextChanges (
470
488
current : Fiber ,
471
489
workInProgress : Fiber ,
472
490
renderLanes : Lanes ,
491
+ ) {
492
+ const forcePropagateEntireTree = false ;
493
+ propagateParentContextChanges (
494
+ current ,
495
+ workInProgress ,
496
+ renderLanes ,
497
+ forcePropagateEntireTree ,
498
+ ) ;
499
+ }
500
+
501
+ // Used for propagating a deferred tree (Suspense, Offscreen). We must propagate
502
+ // to the entire subtree, because we won't revisit it until after the current
503
+ // render has completed, at which point we'll have lost track of which providers
504
+ // have changed.
505
+ export function propagateParentContextChangesToDeferredTree(
506
+ current: Fiber,
507
+ workInProgress: Fiber,
508
+ renderLanes: Lanes,
509
+ ) {
510
+ const forcePropagateEntireTree = true ;
511
+ propagateParentContextChanges (
512
+ current ,
513
+ workInProgress ,
514
+ renderLanes ,
515
+ forcePropagateEntireTree ,
516
+ ) ;
517
+ }
518
+
519
+ // Used by lazy context propagation algorithm. When we find a context dependency
520
+ // match, we don't propagate the changes any further into that fiber's subtree.
521
+ // We add the matched fibers to this set. Later, if something inside that
522
+ // subtree bails out of rendering, the presence of a parent fiber in this Set
523
+ // tells us that we need to continue propagating.
524
+ //
525
+ // This is a set of _current_ fibers, not work-in-progress fibers. That's why
526
+ // it's a set instead of a flag on the fiber.
527
+ let deferredPropagation: Set< Fiber > | null = null;
528
+
529
+ export function resetDeferredContextPropagation() {
530
+ // This is called by prepareFreshStack
531
+ deferredPropagation = null ;
532
+ }
533
+
534
+ function propagateParentContextChanges(
535
+ current: Fiber,
536
+ workInProgress: Fiber,
537
+ renderLanes: Lanes,
538
+ forcePropagateEntireTree: boolean,
473
539
) {
474
540
if ( ! enableLazyContextPropagation ) {
475
541
return false ;
@@ -479,9 +545,42 @@ export function lazilyPropagateParentContextChanges(
479
545
// number, we use an Array instead of Set.
480
546
let contexts = null;
481
547
let parent = workInProgress;
482
- while (parent !== null && ( parent . flags & DidPropagateContext ) === NoFlags ) {
548
+ let isInsidePropagationBailout = false;
549
+ while (parent !== null) {
550
+ const currentParent = parent . alternate ;
551
+ invariant (
552
+ currentParent !== null ,
553
+ 'Should have a current fiber. This is a bug in React.' ,
554
+ ) ;
555
+
556
+ if ( ! isInsidePropagationBailout ) {
557
+ if ( deferredPropagation === null ) {
558
+ if ( ( parent . flags & DidPropagateContext ) !== NoFlags ) {
559
+ break ;
560
+ }
561
+ } else {
562
+ if ( currentParent !== null && deferredPropagation . has ( currentParent ) ) {
563
+ // We're inside a subtree that previously bailed out of propagation.
564
+ // We must disregard the the DidPropagateContext flag as we continue
565
+ // searching for parent providers.
566
+ isInsidePropagationBailout = true ;
567
+ // We know that none of the providers in between the propagation
568
+ // bailout and the nearest render bailout above that could have
569
+ // changed. So we can skip those.
570
+ do {
571
+ parent = parent . return ;
572
+ invariant (
573
+ parent !== null ,
574
+ 'Expected to find a bailed out fiber. This is a bug in React.' ,
575
+ ) ;
576
+ } while ( ( parent . flags & DidPropagateContext ) === NoFlags ) ;
577
+ } else if ((parent.flags & DidPropagateContext ) !== NoFlags ) {
578
+ break ;
579
+ }
580
+ }
581
+ }
582
+
483
583
if ( parent . tag === ContextProvider ) {
484
- const currentParent = parent . alternate ;
485
584
if ( currentParent !== null ) {
486
585
const oldProps = currentParent . memoizedProps ;
487
586
if ( oldProps !== null ) {
@@ -510,15 +609,33 @@ export function lazilyPropagateParentContextChanges(
510
609
if ( contexts !== null ) {
511
610
// If there were any changed providers, search through the children and
512
611
// propagate their changes.
513
- propagateContextChanges ( workInProgress , contexts , renderLanes ) ;
612
+ propagateContextChanges (
613
+ workInProgress ,
614
+ contexts ,
615
+ renderLanes ,
616
+ forcePropagateEntireTree ,
617
+ ) ;
514
618
}
515
619
516
- // This is an optimization so that we only propagate once per subtree. (We
517
- // will propagate the same providers to different subtrees, though — that's
518
- // why the flag is on the fiber that bailed out, not the provider.) If a
620
+ // This is an optimization so that we only propagate once per subtree. If a
519
621
// deeply nested child bails out, and it calls this propagation function, it
520
622
// uses this flag to know that the remaining ancestor providers have already
521
623
// been propagated.
624
+ //
625
+ // NOTE: This optimization is only necessary because we sometimes enter the
626
+ // begin phase of nodes that don't have any work scheduled on them —
627
+ // specifically, the siblings of a node that _does_ have scheduled work. The
628
+ // siblings will bail out and call this function again, even though we already
629
+ // propagated content changes to it and its subtree. So we use this flag to
630
+ // mark that the parent providers already propagated.
631
+ //
632
+ // Unfortunately, though, we need to ignore this flag when we're inside a
633
+ // tree whose context propagation was deferred — that's what the
634
+ // `deferredPropagation` set is for.
635
+ //
636
+ // If we could instead bail out before entering the siblings' beging phase,
637
+ // then we could remove both `DidPropagateContext` and `deferredPropagation`.
638
+ // Consider this as part of the next refactor to the fiber tree structure.
522
639
workInProgress.flags |= DidPropagateContext;
523
640
}
524
641
0 commit comments