Skip to content

Commit 78be8f6

Browse files
committed
Store list of contexts on the fiber
Currently, context can only be read by a special type of component, ContextConsumer. We want to add support to all fibers, including classes and functional components. Each fiber may read from one or more contexts. To enable quick, mono- morphic access of this list, we'll store them on a fiber property.
1 parent 6731bfb commit 78be8f6

File tree

4 files changed

+210
-149
lines changed

4 files changed

+210
-149
lines changed

packages/react-reconciler/src/ReactFiber.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {TypeOfMode} from './ReactTypeOfMode';
1414
import type {TypeOfSideEffect} from 'shared/ReactTypeOfSideEffect';
1515
import type {ExpirationTime} from './ReactFiberExpirationTime';
1616
import type {UpdateQueue} from './ReactUpdateQueue';
17+
import type {ContextReader} from './ReactFiberNewContext';
1718

1819
import invariant from 'shared/invariant';
1920
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
@@ -124,6 +125,9 @@ export type Fiber = {|
124125
// The state used to create the output
125126
memoizedState: any,
126127

128+
// A linked-list of contexts that this fiber depends on
129+
firstContextReader: ContextReader<mixed> | null,
130+
127131
// Bitfield that describes properties about the fiber and its subtree. E.g.
128132
// the AsyncMode flag indicates whether the subtree should be async-by-
129133
// default. When a fiber is created, it inherits the mode of its
@@ -213,6 +217,7 @@ function FiberNode(
213217
this.memoizedProps = null;
214218
this.updateQueue = null;
215219
this.memoizedState = null;
220+
this.firstContextReader = null;
216221

217222
this.mode = mode;
218223

@@ -331,6 +336,7 @@ export function createWorkInProgress(
331336
workInProgress.memoizedProps = current.memoizedProps;
332337
workInProgress.memoizedState = current.memoizedState;
333338
workInProgress.updateQueue = current.updateQueue;
339+
workInProgress.firstContextReader = current.firstContextReader;
334340

335341
// These will be overridden during the parent's reconciliation
336342
workInProgress.sibling = current.sibling;
@@ -562,6 +568,7 @@ export function assignFiberPropertiesInDEV(
562568
target.memoizedProps = source.memoizedProps;
563569
target.updateQueue = source.updateQueue;
564570
target.memoizedState = source.memoizedState;
571+
target.firstContextReader = source.firstContextReader;
565572
target.mode = source.mode;
566573
target.effectTag = source.effectTag;
567574
target.nextEffect = source.nextEffect;

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 16 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,11 @@ import {
6969
import {pushHostContext, pushHostContainer} from './ReactFiberHostContext';
7070
import {
7171
pushProvider,
72-
getContextCurrentValue,
73-
getContextChangedBits,
72+
propagateContextChange,
73+
checkForPendingContext,
74+
readContext,
75+
prepareToReadContext,
76+
finishReadingContext,
7477
} from './ReactFiberNewContext';
7578
import {
7679
markActualRenderTimeStarted,
@@ -764,100 +767,6 @@ function updatePortalComponent(current, workInProgress, renderExpirationTime) {
764767
return workInProgress.child;
765768
}
766769

767-
function propagateContextChange<V>(
768-
workInProgress: Fiber,
769-
context: ReactContext<V>,
770-
changedBits: number,
771-
renderExpirationTime: ExpirationTime,
772-
): void {
773-
let fiber = workInProgress.child;
774-
if (fiber !== null) {
775-
// Set the return pointer of the child to the work-in-progress fiber.
776-
fiber.return = workInProgress;
777-
}
778-
while (fiber !== null) {
779-
let nextFiber;
780-
// Visit this fiber.
781-
switch (fiber.tag) {
782-
case ContextConsumer:
783-
// Check if the context matches.
784-
const observedBits: number = fiber.stateNode | 0;
785-
if (fiber.type === context && (observedBits & changedBits) !== 0) {
786-
// Update the expiration time of all the ancestors, including
787-
// the alternates.
788-
let node = fiber;
789-
while (node !== null) {
790-
const alternate = node.alternate;
791-
if (
792-
node.expirationTime === NoWork ||
793-
node.expirationTime > renderExpirationTime
794-
) {
795-
node.expirationTime = renderExpirationTime;
796-
if (
797-
alternate !== null &&
798-
(alternate.expirationTime === NoWork ||
799-
alternate.expirationTime > renderExpirationTime)
800-
) {
801-
alternate.expirationTime = renderExpirationTime;
802-
}
803-
} else if (
804-
alternate !== null &&
805-
(alternate.expirationTime === NoWork ||
806-
alternate.expirationTime > renderExpirationTime)
807-
) {
808-
alternate.expirationTime = renderExpirationTime;
809-
} else {
810-
// Neither alternate was updated, which means the rest of the
811-
// ancestor path already has sufficient priority.
812-
break;
813-
}
814-
node = node.return;
815-
}
816-
// Don't scan deeper than a matching consumer. When we render the
817-
// consumer, we'll continue scanning from that point. This way the
818-
// scanning work is time-sliced.
819-
nextFiber = null;
820-
} else {
821-
// Traverse down.
822-
nextFiber = fiber.child;
823-
}
824-
break;
825-
case ContextProvider:
826-
// Don't scan deeper if this is a matching provider
827-
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
828-
break;
829-
default:
830-
// Traverse down.
831-
nextFiber = fiber.child;
832-
break;
833-
}
834-
if (nextFiber !== null) {
835-
// Set the return pointer of the child to the work-in-progress fiber.
836-
nextFiber.return = fiber;
837-
} else {
838-
// No child. Traverse to next sibling.
839-
nextFiber = fiber;
840-
while (nextFiber !== null) {
841-
if (nextFiber === workInProgress) {
842-
// We're back to the root of this subtree. Exit.
843-
nextFiber = null;
844-
break;
845-
}
846-
let sibling = nextFiber.sibling;
847-
if (sibling !== null) {
848-
// Set the return pointer of the sibling to the work-in-progress fiber.
849-
sibling.return = nextFiber.return;
850-
nextFiber = sibling;
851-
break;
852-
}
853-
// No more siblings. Traverse up.
854-
nextFiber = nextFiber.return;
855-
}
856-
}
857-
fiber = nextFiber;
858-
}
859-
}
860-
861770
function updateContextProvider(current, workInProgress, renderExpirationTime) {
862771
const providerType: ReactProviderType<any> = workInProgress.type;
863772
const context: ReactContext<any> = providerType._context;
@@ -970,42 +879,16 @@ function updateContextConsumer(current, workInProgress, renderExpirationTime) {
970879
const newProps = workInProgress.pendingProps;
971880
const oldProps = workInProgress.memoizedProps;
972881

973-
const newValue = getContextCurrentValue(context);
974-
const changedBits = getContextChangedBits(context);
975-
976-
if (hasLegacyContextChanged()) {
977-
// Normally we can bail out on props equality but if context has changed
978-
// we don't do the bailout and we have to reuse existing props instead.
979-
} else if (changedBits === 0 && oldProps === newProps) {
980-
return bailoutOnAlreadyFinishedWork(current, workInProgress);
981-
}
982-
workInProgress.memoizedProps = newProps;
983-
984-
let observedBits = newProps.unstable_observedBits;
985-
if (observedBits === undefined || observedBits === null) {
986-
// Subscribe to all changes by default
987-
observedBits = MAX_SIGNED_31_BIT_INT;
882+
if (!checkForPendingContext(workInProgress, renderExpirationTime)) {
883+
if (hasLegacyContextChanged()) {
884+
// Normally we can bail out on props equality but if context has changed
885+
// we don't do the bailout and we have to reuse existing props instead.
886+
} else if (oldProps === newProps) {
887+
return bailoutOnAlreadyFinishedWork(current, workInProgress);
888+
}
988889
}
989-
// Store the observedBits on the fiber's stateNode for quick access.
990-
workInProgress.stateNode = observedBits;
991890

992-
if ((changedBits & observedBits) !== 0) {
993-
// Context change propagation stops at matching consumers, for time-
994-
// slicing. Continue the propagation here.
995-
propagateContextChange(
996-
workInProgress,
997-
context,
998-
changedBits,
999-
renderExpirationTime,
1000-
);
1001-
} else if (oldProps === newProps) {
1002-
// Skip over a memoized parent with a bitmask bailout even
1003-
// if we began working on it because of a deeper matching child.
1004-
return bailoutOnAlreadyFinishedWork(current, workInProgress);
1005-
}
1006-
// There is no bailout on `children` equality because we expect people
1007-
// to often pass a bound method as a child, but it may reference
1008-
// `this.state` or `this.props` (and thus needs to re-render on `setState`).
891+
workInProgress.memoizedProps = newProps;
1009892

1010893
const render = newProps.children;
1011894

@@ -1019,6 +902,8 @@ function updateContextConsumer(current, workInProgress, renderExpirationTime) {
1019902
);
1020903
}
1021904

905+
prepareToReadContext();
906+
const newValue = readContext(context, newProps.unstable_observedBits);
1022907
let newChildren;
1023908
if (__DEV__) {
1024909
ReactCurrentOwner.current = workInProgress;
@@ -1028,6 +913,7 @@ function updateContextConsumer(current, workInProgress, renderExpirationTime) {
1028913
} else {
1029914
newChildren = render(newValue);
1030915
}
916+
workInProgress.firstContextReader = finishReadingContext();
1031917

1032918
// React DevTools reads this flag.
1033919
workInProgress.effectTag |= PerformedWork;

0 commit comments

Comments
 (0)