共计 6635 个字符,预计需要花费 17 分钟才能阅读完成。
react 源码解析 17.context
视频解说(高效学习):进入学习
往期文章:
1. 开篇介绍和面试题
2.react 的设计理念
3.react 源码架构
4. 源码目录构造和调试
5.jsx& 外围 api
6.legacy 和 concurrent 模式入口函数
7.Fiber 架构
8.render 阶段
9.diff 算法
10.commit 阶段
11. 生命周期
12. 状态更新流程
13.hooks 源码
14. 手写 hooks
15.scheduler&Lane
16.concurrent 模式
17.context
18 事件零碎
19. 手写迷你版 react
20. 总结 & 第一章的面试题解答
21.demo
查看视频调试 demo_7
context 流程图
cursor/valueStack
react 源码中存在一个 valueStack 和 valueCursor 用来记录 context 的历史信息和以后 context,另外还有一个 didPerformWorkStackCursor 用来示意以后的 context 有没有变动
//ReactFiberNewContext.new.js
const valueCursor: StackCursor<mixed> = createCursor(null);
const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
//ReactFiberStack.new.js
const valueStack: Array<any> = [];
function pushProvider(providerFiber, nextValue) {
var context = providerFiber.type._context;
{push(valueCursor, context._currentValue, providerFiber);
context._currentValue = nextValue;
}
}
function popProvider(providerFiber) {
var currentValue = valueCursor.current;
pop(valueCursor, providerFiber);
var context = providerFiber.type._context;
{context._currentValue = currentValue;}
}
- 在 render 阶段调用 updateContextProvider 的时候会执行 pushProvider,将新的值 push 进 valueStack 中
- 在 commit 阶段调用 completeWork 的时候会执行 popProvider,将栈顶 context pop 进去,
为什么会有这样一个机制呢,因为咱们的 context 是跨层级的,在之前讲到 render 阶段和 commit 阶段的时候,咱们会以深度优先遍历的形式遍历节点,如果波及跨层级读取状态就有点力不从心了,就须要一层一层往下传递咱们的 props,所以咱们能够用一个 stack 记录咱们的 context,在 render 阶段 pushProvider,在 commit 阶段 popProvider,在每个具体的层级能依据 valueCursor 取以后 value
createContext
export function createContext<T>(
defaultValue: T,
calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {if (calculateChangedBits === undefined) {// 能够传入计算 bit 的函数
calculateChangedBits = null;
} else {//...}
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_calculateChangedBits: calculateChangedBits,// 计算 value 变动的函数
_currentValue: defaultValue,//dom 环境的 value
_currentValue2: defaultValue,//art 环境的 value
_threadCount: 0,
Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
if (__DEV__) { } else {context.Consumer = context;}
return context;
}
// 示例
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;
});
在简化之后的 createContext 中能够看到,context 和 Provider、Consumer 的关系是这样的:
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
context.Consumer = context;
useContext
useContext 会调用 readContext,readContext 会创立 dependce,退出以后 fiber 的 dependencies 链表中
function readContext(context, observedBits) {
{if (lastContextWithAllBitsObserved === context) ; else if (observedBits === false || observedBits === 0) ; else {
var resolvedObservedBits;
// 生成 resolvedObservedBits
if (typeof observedBits !== 'number' || observedBits === MAX_SIGNED_31_BIT_INT) {
lastContextWithAllBitsObserved = context;
resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
} else {resolvedObservedBits = observedBits;}
var contextItem = {// 生成 dependce
context: context,
observedBits: resolvedObservedBits,
next: null
};
if (lastContextDependency === null) {
//...
lastContextDependency = contextItem;
currentlyRenderingFiber.dependencies = {//dependencies 链表的第一个
lanes: NoLanes,
firstContext: contextItem,
responders: null
};
} else {lastContextDependency = lastContextDependency.next = contextItem;// 退出 dependencies 链表}
}
return context._currentValue ;
}
provider/customer
在 render 阶段会调用 updateContextProvider,留神几个要害的步骤
- pushProvider:将以后 context 退出 valueStack
- calculateChangedBits:useContext 能够设置 observedBits,没有设置的话就是 MAX_SIGNED_31_BIT_INT,也就是 31 位 1,用于计算 changedBits,这个计算 context 是否变动的过程就产生在 calculateChangedBits 函数中,用这样的形式能够进步 context 变动之后的性能
- bailoutOnAlreadyFinishedWork/propagateContextChange:如果 changedBits 没有扭转则走 bailoutOnAlreadyFinishedWork 的逻辑,跳过以后节点的更新,如果扭转则执行 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) {//context 没有扭转
if (oldProps.children === newProps.children && !hasContextChanged()) {return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
} else {//context 扭转了
propagateContextChange(workInProgress, context, changedBits, renderLanes);
}
}
var newChildren = newProps.children;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
function calculateChangedBits(context, newValue, oldValue) {if (objectIs(oldValue, newValue)) {
// 没有扭转
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;
}
}
// 示例
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;
});
function propagateContextChange(workInProgress, context, changedBits, renderLanes) {
var fiber = workInProgress.child;
if (fiber !== null) {fiber.return = workInProgress;//fiber 不存在 就找父节点}
while (fiber !== null) {
var nextFiber = void 0;// 遍历 fiber
var list = fiber.dependencies;
if (list !== null) {
nextFiber = fiber.child;
var dependency = list.firstContext;
while (dependency !== null) {// 遍历 dependencies 链表
if (dependency.context === context && (dependency.observedBits & changedBits) !== 0) {
// 有变动
if (fiber.tag === ClassComponent) {
// 创立新的 update
var update = createUpdate(NoTimestamp, pickArbitraryLane(renderLanes));
update.tag = ForceUpdate;
enqueueUpdate(fiber, update);
}
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);// 合并优先级
var alternate = fiber.alternate;
if (alternate !== null) {alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
scheduleWorkOnParentPath(fiber.return, renderLanes); // 更新先人节点的优先级
list.lanes = mergeLanes(list.lanes, renderLanes);
break;
}
dependency = dependency.next;
}
}
//...
nextFiber = fiber.sibling;
} else {nextFiber = fiber.child;}
//...
fiber = nextFiber;
}
}
updateContextConsumer 要害的代码如下,执行 prepareToReadContext 判断优先级是否足够退出以后这次 render,readContext 取到以后 context 的 value
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;
}