@@ -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 ;
@@ -426,7 +448,7 @@ function propagateContextChanges<T>(
426
448
// on its children. We'll use the childLanes on
427
449
// this fiber to indicate that a context has changed.
428
450
scheduleWorkOnParentPath(parentSuspense, renderLanes);
429
- nextFiber = fiber.sibling ;
451
+ nextFiber = null ;
430
452
} else {
431
453
// Traverse down.
432
454
nextFiber = fiber . child ;
@@ -459,14 +481,58 @@ function propagateContextChanges<T>(
459
481
}
460
482
}
461
483
462
- // Alias for propagating a deferred tree (Suspense, Offscreen). Currently it's
463
- // the same algorithm but there may be a way to optimize one or the other.
464
- export const propagateParentContextChangesToDeferredTree = lazilyPropagateParentContextChanges ;
465
-
466
484
export function lazilyPropagateParentContextChanges (
467
485
current : Fiber ,
468
486
workInProgress : Fiber ,
469
487
renderLanes : Lanes ,
488
+ ) {
489
+ const forcePropagateEntireTree = false ;
490
+ propagateParentContextChanges (
491
+ current ,
492
+ workInProgress ,
493
+ renderLanes ,
494
+ forcePropagateEntireTree ,
495
+ ) ;
496
+ }
497
+
498
+ // Used for propagating a deferred tree (Suspense, Offscreen). We must propagate
499
+ // to the entire subtree, because we won't revisit it until after the current
500
+ // render has completed, at which point we'll have lost track of which providers
501
+ // have changed.
502
+ export function propagateParentContextChangesToDeferredTree(
503
+ current: Fiber,
504
+ workInProgress: Fiber,
505
+ renderLanes: Lanes,
506
+ ) {
507
+ const forcePropagateEntireTree = true ;
508
+ propagateParentContextChanges (
509
+ current ,
510
+ workInProgress ,
511
+ renderLanes ,
512
+ forcePropagateEntireTree ,
513
+ ) ;
514
+ }
515
+
516
+ // Used by lazy context propagation algorithm. When we find a context dependency
517
+ // match, we don't propagate the changes any further into that fiber's subtree.
518
+ // We add the matched fibers to this set. Later, if something inside that
519
+ // subtree bails out of rendering, the presence of a parent fiber in this Set
520
+ // tells us that we need to continue propagating.
521
+ //
522
+ // This is a set of _current_ fibers, not work-in-progress fibers. That's why
523
+ // it's a set instead of a flag on the fiber.
524
+ let deferredPropagation: Set< Fiber > | null = null;
525
+
526
+ export function resetDeferredContextPropagation() {
527
+ // This is called by prepareFreshStack
528
+ deferredPropagation = null ;
529
+ }
530
+
531
+ function propagateParentContextChanges(
532
+ current: Fiber,
533
+ workInProgress: Fiber,
534
+ renderLanes: Lanes,
535
+ forcePropagateEntireTree: boolean,
470
536
) {
471
537
if ( ! enableLazyContextPropagation ) {
472
538
return false ;
@@ -476,9 +542,42 @@ export function lazilyPropagateParentContextChanges(
476
542
// number, we use an Array instead of Set.
477
543
let contexts = null;
478
544
let parent = workInProgress;
479
- while (parent !== null && ( parent . flags & DidPropagateContext ) === NoFlags ) {
545
+ let isInsidePropagationBailout = false;
546
+ while (parent !== null) {
547
+ const currentParent = parent . alternate ;
548
+ invariant (
549
+ currentParent !== null ,
550
+ 'Should have a current fiber. This is a bug in React.' ,
551
+ ) ;
552
+
553
+ if ( ! isInsidePropagationBailout ) {
554
+ if ( deferredPropagation === null ) {
555
+ if ( ( parent . flags & DidPropagateContext ) !== NoFlags ) {
556
+ break ;
557
+ }
558
+ } else {
559
+ if ( currentParent !== null && deferredPropagation . has ( currentParent ) ) {
560
+ // We're inside a subtree that previously bailed out of propagation.
561
+ // We must disregard the the DidPropagateContext flag as we continue
562
+ // searching for parent providers.
563
+ isInsidePropagationBailout = true ;
564
+ // We know that none of the providers in between the propagation
565
+ // bailout and the nearest render bailout above that could have
566
+ // changed. So we can skip those.
567
+ do {
568
+ parent = parent . return ;
569
+ invariant (
570
+ parent !== null ,
571
+ 'Expected to find a bailed out fiber. This is a bug in React.' ,
572
+ ) ;
573
+ } while ( ( parent . flags & DidPropagateContext ) === NoFlags ) ;
574
+ } else if ((parent.flags & DidPropagateContext ) !== NoFlags ) {
575
+ break ;
576
+ }
577
+ }
578
+ }
579
+
480
580
if ( parent . tag === ContextProvider ) {
481
- const currentParent = parent . alternate ;
482
581
if ( currentParent !== null ) {
483
582
const oldProps = currentParent . memoizedProps ;
484
583
if ( oldProps !== null ) {
@@ -507,15 +606,33 @@ export function lazilyPropagateParentContextChanges(
507
606
if ( contexts !== null ) {
508
607
// If there were any changed providers, search through the children and
509
608
// propagate their changes.
510
- propagateContextChanges ( workInProgress , contexts , renderLanes ) ;
609
+ propagateContextChanges (
610
+ workInProgress ,
611
+ contexts ,
612
+ renderLanes ,
613
+ forcePropagateEntireTree ,
614
+ ) ;
511
615
}
512
616
513
- // This is an optimization so that we only propagate once per subtree. (We
514
- // will propagate the same providers to different subtrees, though — that's
515
- // why the flag is on the fiber that bailed out, not the provider.) If a
617
+ // This is an optimization so that we only propagate once per subtree. If a
516
618
// deeply nested child bails out, and it calls this propagation function, it
517
619
// uses this flag to know that the remaining ancestor providers have already
518
620
// been propagated.
621
+ //
622
+ // NOTE: This optimization is only necessary because we sometimes enter the
623
+ // begin phase of nodes that don't have any work scheduled on them —
624
+ // specifically, the siblings of a node that _does_ have scheduled work. The
625
+ // siblings will bail out and call this function again, even though we already
626
+ // propagated content changes to it and its subtree. So we use this flag to
627
+ // mark that the parent providers already propagated.
628
+ //
629
+ // Unfortunately, though, we need to ignore this flag when we're inside a
630
+ // tree whose context propagation was deferred — that's what the
631
+ // `deferredPropagation` set is for.
632
+ //
633
+ // If we could instead bail out before entering the siblings' beging phase,
634
+ // then we could remove both `DidPropagateContext` and `deferredPropagation`.
635
+ // Consider this as part of the next refactor to the fiber tree structure.
519
636
workInProgress.flags |= DidPropagateContext;
520
637
}
521
638
0 commit comments