@@ -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.
@@ -746,13 +752,17 @@ export function prepareToReadContext(
746752
747753export function readContextAndCompare< C > (
748754 context: ReactContext< C > ,
749- compare: void | (C => mixed ) ,
755+ compare: (C => mixed ) | null ,
750756) : C {
751757 if ( ! enableLazyContextPropagation ) {
752758 return readContext ( context ) ;
753759 }
754760
755- return readContextForConsumer(currentlyRenderingFiber, context, compare);
761+ return readContextForConsumer_withCompare(
762+ currentlyRenderingFiber,
763+ context,
764+ compare,
765+ );
756766}
757767
758768export function readContext < T > (context: ReactContext< T > ): T {
@@ -782,12 +792,12 @@ export function readContextDuringReconciliation<T>(
782792 return readContextForConsumer(consumer, context);
783793}
784794
785- type ContextCompare < C , S > = C => S ;
795+ type ContextCompare < C , V > = C => V | null ;
786796
787- function readContextForConsumer < C , S > (
797+ function readContextForConsumer_withCompare < C , S > (
788798 consumer: Fiber | null,
789799 context: ReactContext< C > ,
790- compare?: void | (C => S ) ,
800+ compare: (C => S ) | null ,
791801) : C {
792802 const value = isPrimaryRenderer
793803 ? context . _currentValue
@@ -800,7 +810,7 @@ function readContextForConsumer<C, S>(
800810 context : ( ( context : any ) : ReactContext < mixed > ) ,
801811 memoizedValue : value ,
802812 next : null ,
803- compare : ( ( compare : any ) : ContextCompare < mixed , mixed > | null ) ,
813+ compare : compare ? ( ( compare : any ) : ContextCompare < mixed , mixed > ) : null ,
804814 lastComparedValue : compare != null ? compare ( value ) : null ,
805815 } ;
806816
@@ -830,3 +840,47 @@ function readContextForConsumer<C, S>(
830840 }
831841 return value ;
832842}
843+
844+ function readContextForConsumer < C > (
845+ consumer: Fiber | null,
846+ context: ReactContext< C > ,
847+ ): C {
848+ const value = isPrimaryRenderer
849+ ? context . _currentValue
850+ : context . _currentValue2 ;
851+
852+ if ( lastFullyObservedContext === context ) {
853+ // Nothing to do. We already observe everything in this context.
854+ } else {
855+ const contextItem = {
856+ context : ( ( context : any ) : ReactContext < mixed > ) ,
857+ memoizedValue : value ,
858+ next : null ,
859+ } ;
860+
861+ if ( lastContextDependency === null ) {
862+ if ( consumer === null ) {
863+ throw new Error (
864+ 'Context can only be read while React is rendering. ' +
865+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
866+ 'In function components, you can read it directly in the function body, but not ' +
867+ 'inside Hooks like useReducer() or useMemo().' ,
868+ ) ;
869+ }
870+
871+ // This is the first dependency for this component. Create a new list.
872+ lastContextDependency = contextItem;
873+ consumer.dependencies = {
874+ lanes : NoLanes ,
875+ firstContext : contextItem ,
876+ } ;
877+ if (enableLazyContextPropagation) {
878+ consumer . flags |= NeedsPropagation ;
879+ }
880+ } else {
881+ // Append a new context item.
882+ lastContextDependency = lastContextDependency . next = contextItem ;
883+ }
884+ }
885+ return value ;
886+ }
0 commit comments