Video Course (Efficient Learning) : [Enter the course]
(xiaochen1024.com/series/60b1…).
The context diagram
cursor/valueStack
React in a source valueStack and valueCursor used to record the context of history and the current context, another didPerformWorkStackCursor used to say there are any changes in the current context
//ReactFiberNewContext.new.js
const valueCursor: StackCursor<mixed> = createCursor(null);
Copy the code
const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
Copy the code
//ReactFiberStack.new.js
const valueStack: Array<any> = [];
Copy the code
function pushProvider(providerFiber, nextValue) {
varcontext = providerFiber.type._context; { push(valueCursor, context._currentValue, providerFiber); context._currentValue = nextValue; }}Copy the code
function popProvider(providerFiber) {
var currentValue = valueCursor.current;
pop(valueCursor, providerFiber);
varcontext = providerFiber.type._context; { context._currentValue = currentValue; }}Copy the code
-
PushProvider is executed when updateContextProvider is called in the Render phase, pushing the new value into valueStack
-
PopProvider is performed when the completeWork is called in the COMMIT phase, pop the context out of the top of the stack,
In the render and Commit phases, we used depth-first traversal of the nodes. If it involves reading state across layers, we need to pass our props layer by layer. So we can use a stack to record our context, pushProvider in the Render phase, popProvider in the commit phase, and take the current value according to valueCursor at each specific level
createContext
export function createContext<T> (defaultValue: T, calculateChangedBits: ? (a: T, b: T) => number,) :ReactContext<T> {
if (calculateChangedBits === undefined) {// We can pass in a function that evaluates bit
calculateChangedBits = null;
} else {
/ /...
}
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_calculateChangedBits: calculateChangedBits,// Compute a function that changes value
_currentValue: defaultValue,// Dom environment value
_currentValue2: defaultValue,// Value of the art environment
_threadCount: 0.Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
if (__DEV__) {
} else {
context.Consumer = context;
}
return context;
}
/ / sample
const NameChangedBits = 0b01;
const AgeChangedBits = 0b10;
const AppContext = createContext({}, (prevValue, nextValue) = > {
let result = 0;
if(prevValue.name ! == nextValue.name) { result |= NameChangedBits; };if(prevValue.age ! == nextValue.age) { result |= AgeChangedBits; };return result;
});
Copy the code
As you can see in the simplified createContext, the relationship between context and Provider and Consumer looks like this:
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
context.Consumer = context;
Copy the code
useContext
UseContext calls readContext, which creates a Dependce in the dependencies list of the current Fiber
function readContext(context, observedBits) {{if (lastContextWithAllBitsObserved === context) ; else if (observedBits === false || observedBits === 0);else {
var resolvedObservedBits;
/ / generated resolvedObservedBits
if (typeofobservedBits ! = ='number' || observedBits === MAX_SIGNED_31_BIT_INT) {
lastContextWithAllBitsObserved = context;
resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
} else {
resolvedObservedBits = observedBits;
}
var contextItem = {/ / generated dependce
context: context,
observedBits: resolvedObservedBits,
next: null
};
if (lastContextDependency === null) {
/ /...
lastContextDependency = contextItem;
currentlyRenderingFiber.dependencies = {// list of dependencies
lanes: NoLanes,
firstContext: contextItem,
responders: null
};
} else {
lastContextDependency = lastContextDependency.next = contextItem;// Add to the dependencies list}}return context._currentValue ;
}
Copy the code
provider/customer
The updateContextProvider is called in the Render phase, noting a few key steps
-
PushProvider: adds the current context to valueStack
-
CalculateChangedBits: UseContext can set observedBits, otherwise MAX_SIGNED_31_BIT_INT, which is 31 bits 1 for calculating changedBits, This calculation of whether or not the context has changed occurs in the calculateChangedBits function, which improves performance after the context has changed
-
BailoutOnAlreadyFinishedWork/propagateContextChange: If changedBits does not change bailoutOnAlreadyFinishedWork logic, skip the update of the current node, if change the execution propagateContextChange
function updateContextProvider(current, workInProgress, renderLanes) {
var providerType = workInProgress.type;
var context = providerType._context;
var newProps = workInProgress.pendingProps;
var oldProps = workInProgress.memoizedProps;
var newValue = newProps.value;
/ /...
pushProvider(workInProgress, newValue);
if(oldProps ! = =null) {
var oldValue = oldProps.value;
var changedBits = calculateChangedBits(context, newValue, oldValue);
if (changedBits === 0) {// The context has not changed
if(oldProps.children === newProps.children && ! hasContextChanged()) {returnbailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); }}else {/ / the context has changedpropagateContextChange(workInProgress, context, changedBits, renderLanes); }}var newChildren = newProps.children;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
Copy the code
function calculateChangedBits(context, newValue, oldValue) {
if (objectIs(oldValue, newValue)) {
// No change
return 0;
} else {
var changedBits = typeof context._calculateChangedBits === 'function' ? context._calculateChangedBits(oldValue, newValue) : MAX_SIGNED_31_BIT_INT;
{
if((changedBits & MAX_SIGNED_31_BIT_INT) ! == changedBits) { error('calculateChangedBits: Expected the return value to be a ' + '31-bit integer. Instead received: %s', changedBits); }}return changedBits | 0; }}/ / sample
const NameChangedBits = 0b01;
const AgeChangedBits = 0b10;
const AppContext = createContext({}, (prevValue, nextValue) = > {
let result = 0;
if(prevValue.name ! == nextValue.name) { result |= NameChangedBits; };if(prevValue.age ! == nextValue.age) { result |= AgeChangedBits; };return result;
});
Copy the code
function propagateContextChange(workInProgress, context, changedBits, renderLanes) {
var fiber = workInProgress.child;
if(fiber ! = =null) {
fiber.return = workInProgress;// If fiber does not exist, find the parent node
}
while(fiber ! = =null) {
var nextFiber = void 0;/ / traverse fiber
var list = fiber.dependencies;
if(list ! = =null) {
nextFiber = fiber.child;
var dependency = list.firstContext;
while(dependency ! = =null) {// run the list of dependencies
if(dependency.context === context && (dependency.observedBits & changedBits) ! = =0) {
/ / a change
if (fiber.tag === ClassComponent) {
// Create a new update
var update = createUpdate(NoTimestamp, pickArbitraryLane(renderLanes));
update.tag = ForceUpdate;
enqueueUpdate(fiber, update);
}
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);// Merge priorities
var alternate = fiber.alternate;
if(alternate ! = =null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
scheduleWorkOnParentPath(fiber.return, renderLanes); // Update the priority of the ancestor node
list.lanes = mergeLanes(list.lanes, renderLanes);
break; } dependency = dependency.next; }}/ /...
nextFiber = fiber.sibling;
} else {
nextFiber = fiber.child;
}
/ /...fiber = nextFiber; }}Copy the code
The key code for updateContextConsumer is as follows: Execute prepareToReadContext to determine if the priority is sufficient to add the current Render, and readContext takes the value of the current context
function updateContextConsumer(current, workInProgress, renderLanes) {
var context = workInProgress.type;
/ /...
prepareToReadContext(workInProgress, renderLanes);
var newValue = readContext(context, newProps.unstable_observedBits);
var newChildren;
{
ReactCurrentOwner$1.current = workInProgress;
setIsRendering(true);
newChildren = render(newValue);
setIsRendering(false);
}
/ /...
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
Copy the code