@@ -12,6 +12,7 @@ import type {
1212 Fiber ,
1313 ContextDependency ,
1414 Dependencies ,
15+ ContextDependencyWithCompare ,
1516} from './ReactInternalTypes' ;
1617import type { StackCursor } from './ReactFiberStack' ;
1718import type { Lanes } from './ReactFiberLane' ;
@@ -72,7 +73,10 @@ if (__DEV__) {
7273}
7374
7475let currentlyRenderingFiber: Fiber | null = null;
75- let lastContextDependency: ContextDependency< mixed , mixed > | null = null;
76+ let lastContextDependency:
77+ | ContextDependency< mixed >
78+ | ContextDependencyWithCompare< mixed , mixed >
79+ | null = null;
7680let lastFullyObservedContext: ReactContext< any > | null = null;
7781
7882let isDisallowedContextReadInDEV: boolean = false;
@@ -403,19 +407,21 @@ function propagateContextChanges<T>(
403407 const context : ReactContext < T > = contexts [ i ] ;
404408 // Check if the context matches.
405409 if ( dependency . context === context ) {
406- const compare = dependency . compare ;
407- if ( enableContextProfiling && compare != null ) {
408- const newValue = isPrimaryRenderer
409- ? dependency . context . _currentValue
410- : dependency . context . _currentValue2 ;
411- if (
412- ! checkIfComparedContextValuesChanged (
413- dependency . lastComparedValue ,
414- compare ( newValue ) ,
415- )
416- ) {
417- // Compared value hasn't changed. Bail out early.
418- continue findContext ;
410+ if ( enableContextProfiling ) {
411+ const compare = dependency . compare ;
412+ if ( compare != null ) {
413+ const newValue = isPrimaryRenderer
414+ ? dependency . context . _currentValue
415+ : dependency . context . _currentValue2 ;
416+ if (
417+ ! checkIfComparedContextValuesChanged (
418+ dependency . lastComparedValue ,
419+ compare ( newValue ) ,
420+ )
421+ ) {
422+ // Compared value hasn't changed. Bail out early.
423+ continue findContext;
424+ }
419425 }
420426 }
421427 // Match! Schedule an update on this fiber.
@@ -697,12 +703,11 @@ export function checkIfContextChanged(
697703 ? context . _currentValue
698704 : context . _currentValue2 ;
699705 const oldValue = dependency . memoizedValue ;
700- const compare = dependency . compare ;
701- if ( enableContextProfiling && compare != null ) {
706+ if ( enableContextProfiling && dependency . compare != null ) {
702707 if (
703708 checkIfComparedContextValuesChanged (
704709 dependency . lastComparedValue ,
705- compare ( newValue ) ,
710+ dependency . compare ( newValue ) ,
706711 )
707712 ) {
708713 return true ;
@@ -746,13 +751,17 @@ export function prepareToReadContext(
746751
747752export function readContextAndCompare< C > (
748753 context: ReactContext< C > ,
749- compare: void | (C => mixed ) ,
754+ compare: (C => mixed ) | null ,
750755) : C {
751756 if ( ! enableLazyContextPropagation ) {
752757 return readContext ( context ) ;
753758 }
754759
755- return readContextForConsumer(currentlyRenderingFiber, context, compare);
760+ return readContextForConsumer_withCompare(
761+ currentlyRenderingFiber,
762+ context,
763+ compare,
764+ );
756765}
757766
758767export function readContext < T > (context: ReactContext< T > ): T {
@@ -782,12 +791,12 @@ export function readContextDuringReconciliation<T>(
782791 return readContextForConsumer(consumer, context);
783792}
784793
785- type ContextCompare < C , S > = C => S ;
794+ type ContextCompare < C , V > = C => V | null ;
786795
787- function readContextForConsumer < C , S > (
796+ function readContextForConsumer_withCompare < C , S > (
788797 consumer: Fiber | null,
789798 context: ReactContext< C > ,
790- compare?: void | (C => S ) ,
799+ compare: (C => S ) | null ,
791800) : C {
792801 const value = isPrimaryRenderer
793802 ? context . _currentValue
@@ -800,7 +809,7 @@ function readContextForConsumer<C, S>(
800809 context : ( ( context : any ) : ReactContext < mixed > ) ,
801810 memoizedValue : value ,
802811 next : null ,
803- compare : ( ( compare : any ) : ContextCompare < mixed , mixed > | null ) ,
812+ compare : compare ? ( ( compare : any ) : ContextCompare < mixed , mixed > ) : null ,
804813 lastComparedValue : compare != null ? compare ( value ) : null ,
805814 } ;
806815
@@ -830,3 +839,47 @@ function readContextForConsumer<C, S>(
830839 }
831840 return value ;
832841}
842+
843+ function readContextForConsumer < C > (
844+ consumer: Fiber | null,
845+ context: ReactContext< C > ,
846+ ): C {
847+ const value = isPrimaryRenderer
848+ ? context . _currentValue
849+ : context . _currentValue2 ;
850+
851+ if ( lastFullyObservedContext === context ) {
852+ // Nothing to do. We already observe everything in this context.
853+ } else {
854+ const contextItem = {
855+ context : ( ( context : any ) : ReactContext < mixed > ) ,
856+ memoizedValue : value ,
857+ next : null ,
858+ } ;
859+
860+ if ( lastContextDependency === null ) {
861+ if ( consumer === null ) {
862+ throw new Error (
863+ 'Context can only be read while React is rendering. ' +
864+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
865+ 'In function components, you can read it directly in the function body, but not ' +
866+ 'inside Hooks like useReducer() or useMemo().' ,
867+ ) ;
868+ }
869+
870+ // This is the first dependency for this component. Create a new list.
871+ lastContextDependency = contextItem;
872+ consumer.dependencies = {
873+ lanes : NoLanes ,
874+ firstContext : contextItem ,
875+ } ;
876+ if (enableLazyContextPropagation) {
877+ consumer . flags |= NeedsPropagation ;
878+ }
879+ } else {
880+ // Append a new context item.
881+ lastContextDependency = lastContextDependency . next = contextItem ;
882+ }
883+ }
884+ return value ;
885+ }
0 commit comments