// 创建context
import { createContext } from 'react';
const LevelContext = createContext(1);
// 使用context
import { useContext } from 'react';
function Index() {
const level = useContext(LevelContext);
// ...
return <LevelContext.Provider value={level}>
<Child></Child>
</LevelContext.Provider>
}
function Child() {
const level = useContext(LevelContext);
// ...
return <>
{level}
</>
}
createContext 生产Context
function createContext(defaultValue) {
// TODO: Second argument used to be an optional `calculateChangedBits`
// function. Warn to reserve for future use?
var context = {
$$typeof: REACT_CONTEXT_TYPE,
// As a workaround to support multiple concurrent renderers, we categorize
// some renderers as primary and others as secondary. We only expect
// there to be two concurrent renderers at most: React Native (primary) and
// Fabric (secondary); React DOM (primary) and React ART (secondary).
// Secondary renderers store their context values on separate fields.
_currentValue: defaultValue,
_currentValue2: defaultValue,
// Used to track how many concurrent renderers this context currently
// supports within in a single renderer. Such as parallel server rendering.
_threadCount: 0,
// These are circular
Provider: null,
Consumer: null,
// Add these to use same hidden class in VM as ServerContext
_defaultValue: null,
_globalName: null
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context
};
var hasWarnedAboutUsingNestedContextConsumers = false;
var hasWarnedAboutUsingConsumerProvider = false;
var hasWarnedAboutDisplayNameOnConsumer = false;
{
// A separate object, but proxies back to the original context object for
// backwards compatibility. It has a different $$typeof, so we can properly
// warn for the incorrect usage of Context as a Consumer.
var Consumer = {
$$typeof: REACT_CONTEXT_TYPE,
_context: context
}; // $FlowFixMe[prop-missing]: Flow complains about not setting a value, which is intentional here
Object.defineProperties(Consumer, {
Provider: {
get: function () {
if (!hasWarnedAboutUsingConsumerProvider) {
hasWarnedAboutUsingConsumerProvider = true;
error('Rendering <Context.Consumer.Provider> is not supported and will be removed in ' + 'a future major release. Did you mean to render <Context.Provider> instead?');
}
return context.Provider;
},
set: function (_Provider) {
context.Provider = _Provider;
}
},
_currentValue: {
get: function () {
return context._currentValue;
},
set: function (_currentValue) {
context._currentValue = _currentValue;
}
},
_currentValue2: {
get: function () {
return context._currentValue2;
},
set: function (_currentValue2) {
context._currentValue2 = _currentValue2;
}
},
_threadCount: {
get: function () {
return context._threadCount;
},
set: function (_threadCount) {
context._threadCount = _threadCount;
}
},
Consumer: {
get: function () {
if (!hasWarnedAboutUsingNestedContextConsumers) {
hasWarnedAboutUsingNestedContextConsumers = true;
error('Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' + 'a future major release. Did you mean to render <Context.Consumer> instead?');
}
return context.Consumer;
}
},
displayName: {
get: function () {
return context.displayName;
},
set: function (displayName) {
if (!hasWarnedAboutDisplayNameOnConsumer) {
warn('Setting `displayName` on Context.Consumer has no effect. ' + "You should set it directly on the context with Context.displayName = '%s'.", displayName);
hasWarnedAboutDisplayNameOnConsumer = true;
}
}
}
}); // $FlowFixMe[prop-missing]: Flow complains about missing properties because it doesn't understand defineProperty
context.Consumer = Consumer;
}
{
context._currentRenderer = null;
context._currentRenderer2 = null;
}
return context;
}
Child 消费Context
useContext进入readContextForConsumer
function readContextForConsumer(consumer, context) {
var value = context._currentValue ;
if (lastFullyObservedContext === context) ; else {
var contextItem = {
context: context,
memoizedValue: value,
next: null
};
if (lastContextDependency === null) {
if (consumer === null) {
throw new Error('Context can only be read while React is rendering. ' + 'In classes, you can read it in the render method or getDerivedStateFromProps. ' + 'In function components, you can read it directly in the function body, but not ' + 'inside Hooks like useReducer() or useMemo().');
} // This is the first dependency for this component. Create a new list.
lastContextDependency = contextItem;
consumer.dependencies = {
lanes: NoLanes,
firstContext: contextItem
};
} else {
// Append a new context item.
lastContextDependency = lastContextDependency.next = contextItem;
}
}
return value;
}
Child fiber的dependencies = { lanes: NoLanes, firstContext: contextItem };
关联上了Context
注意dependencies,后面conText更新的时候就是根据这个字段来找到Child的
Index 找到消费者
beginWork LevelContext.Provider 进入updateContextProvider
function updateContextProvider (current, workInProgress, renderLanes) {
// ...
pushProvider(workInProgress, context, newValue);
{
if (oldProps !== null) {
var oldValue = oldProps.value;
if (objectIs(oldValue, newValue)) {
// No change. Bailout early if children are the same.
if (oldProps.children === newProps.children && !hasContextChanged()) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
} else {
// The context value changed. Search for matching consumers and schedule
// them to update.
propagateContextChange(workInProgress, context, renderLanes);
}
}
}
var newChildren = newProps.children;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
pushProvider 会将上下文推入,并且_currentValue取新值
function pushProvider(providerFiber, context, nextValue) {
{
push(valueCursor, context._currentValue, providerFiber);
context._currentValue = nextValue;
{
push(rendererCursorDEV, context._currentRenderer, providerFiber);
if (context._currentRenderer !== undefined && context._currentRenderer !== null && context._currentRenderer !== rendererSigil) {
error('Detected multiple renderers concurrently rendering the ' + 'same context provider. This is currently unsupported.');
}
context._currentRenderer = rendererSigil;
}
}
}
如果value没有发生变化,则和普通的fiber一样
发生变化了进入propagateContextChange
function propagateContextChange(workInProgress, context, renderLanes) {
{
propagateContextChange_eager(workInProgress, context, renderLanes);
}
}
function propagateContextChange_eager(workInProgress, context, renderLanes) {
var fiber = workInProgress.child;
if (fiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
fiber.return = workInProgress;
}
while (fiber !== null) {
var nextFiber = void 0; // Visit this fiber.
var list = fiber.dependencies;
if (list !== null) {
nextFiber = fiber.child;
var dependency = list.firstContext;
while (dependency !== null) {
// Check if the context matches.
if (dependency.context === context) {
// Match! Schedule an update on this fiber.
if (fiber.tag === ClassComponent) {
// Schedule a force update on the work-in-progress.
var lane = pickArbitraryLane(renderLanes);
var update = createUpdate(lane);
update.tag = ForceUpdate; // TODO: Because we don't have a work-in-progress, this will add the
// update to the current fiber, too, which means it will persist even if
// this render is thrown away. Since it's a race condition, not sure it's
// worth fixing.
// Inlined `enqueueUpdate` to remove interleaved update check
var updateQueue = fiber.updateQueue;
if (updateQueue === null) ; else {
var sharedQueue = updateQueue.shared;
var pending = sharedQueue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
}
}
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
var alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
scheduleContextWorkOnParentPath(fiber.return, renderLanes, workInProgress); // Mark the updated lanes on the list, too.
list.lanes = mergeLanes(list.lanes, renderLanes); // Since we already found a match, we can stop traversing the
// dependency list.
break;
}
dependency = dependency.next;
}
} else if (fiber.tag === ContextProvider) {
// Don't scan deeper if this is a matching provider
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
} else if (fiber.tag === DehydratedFragment) {
// If a dehydrated suspense boundary is in this subtree, we don't know
// if it will have any context consumers in it. The best we can do is
// mark it as having updates.
var parentSuspense = fiber.return;
if (parentSuspense === null) {
throw new Error('We just came from a parent so we must have had a parent. This is a bug in React.');
}
parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);
var _alternate = parentSuspense.alternate;
if (_alternate !== null) {
_alternate.lanes = mergeLanes(_alternate.lanes, renderLanes);
} // This is intentionally passing this fiber as the parent
// because we want to schedule this fiber as having work
// on its children. We'll use the childLanes on
// this fiber to indicate that a context has changed.
scheduleContextWorkOnParentPath(parentSuspense, renderLanes, workInProgress);
nextFiber = fiber.sibling;
} else {
// Traverse down.
nextFiber = fiber.child;
}
if (nextFiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
nextFiber.return = fiber;
} else {
// No child. Traverse to next sibling.
nextFiber = fiber;
while (nextFiber !== null) {
if (nextFiber === workInProgress) {
// We're back to the root of this subtree. Exit.
nextFiber = null;
break;
}
var sibling = nextFiber.sibling;
if (sibling !== null) {
// Set the return pointer of the sibling to the work-in-progress fiber.
sibling.return = nextFiber.return;
nextFiber = sibling;
break;
} // No more siblings. Traverse up.
nextFiber = nextFiber.return;
}
}
fiber = nextFiber;
}
}
根据dependencies找到 Child fiber 然后进入scheduleContextWorkOnParentPath
function scheduleContextWorkOnParentPath(parent, renderLanes, propagationRoot) {
// Update the child lanes of all the ancestors, including the alternates.
var node = parent;
while (node !== null) {
var alternate = node.alternate;
if (!isSubsetOfLanes(node.childLanes, renderLanes)) {
node.childLanes = mergeLanes(node.childLanes, renderLanes);
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
}
} else if (alternate !== null && !isSubsetOfLanes(alternate.childLanes, renderLanes)) {
alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
} else ;
if (node === propagationRoot) {
break;
}
node = node.return;
}
{
if (node !== propagationRoot) {
error('Expected to find the propagation root when scheduling context work. ' + 'This error is likely caused by a bug in React. Please file an issue.');
}
}
}
Child 向上一直到Index,中间的childLanes都标记为2。这样beginWith才能进到Child。
之后进入reconcileChildren 和 正常的Index fiber一样,生成新的子fiber
Child消费新的Context
useContext拿到context._currentValue,然后重新渲染