简述
在React
中,有一个valueStack
,是一个栈结构,其中会存入Context
信息,在beginWork
阶段,当Fiber节点为ContextProvider
时,会将当前的Context
的旧值压入栈,并赋予新值,当此Fiber节点执行到completeWork
阶段时,会将旧值弹出,以保证Fiber节点之间的层级关系。
Context
的值就存在Context
对象本身的_currentValue
字段,当Fiber节点读取Context
值时,会直接从Context
上获取值,同时会创建Fiber节点的dependencies
并将Context
信息存入,在Context
值改变时,会从当前ContextProvider
向下遍历,找到所有depenencies
里与Context
相同的Fiber节点,标识它们需要更新。Context
值本身改变是不会触发更新的,依旧需要使用setState
这类方法。
以下源码浅析的React版本为17.0.1,需要先了解Fiber树的构建流程。
valueStack
valueStack
定义在ReactFiberStack.js
文件中, valueStack
存储了几种数据,并不是只存储Context
的值。
export type StackCursor<T> = {|current: T|};
const valueStack: Array<any> = [];
let index = -1;
function createCursor<T>(defaultValue: T): StackCursor<T> {
return {
current: defaultValue,
};
}
function isEmpty(): boolean {
return index === -1;
}
function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
if (index < 0) {
return;
}
cursor.current = valueStack[index];
valueStack[index] = null;
index--;
}
function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
index++;
valueStack[index] = cursor.current;
cursor.current = value;
}
其中有一种数据的类型为StackCursor
,该类型也定义在ReactFiberNewContext.js
文件中,用来存储Context
的新值,它的作用就是传递valueStack
里的值。
// ReactFiberNewContext.js
const valueCursor: StackCursor<mixed> = createCursor(null);
后文有关Context
处理的方法都定义在这个文件里。
从Context
的创建开始看源码。
createContext
createContext
方法实际是创建了一个对象,该对象会作为ReactElement
的type
,同时使用了$$typeof
字段区分REACT_PROVIDER_TYPE
类型和REACT_CONTEXT_TYPE
类型。
export function createContext<T>(
defaultValue: T,
calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
if (calculateChangedBits === undefined) {
calculateChangedBits = null;
}
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_calculateChangedBits: calculateChangedBits,
_currentValue: defaultValue, // context读取的值
_currentValue2: defaultValue,
_threadCount: 0,
Provider: (null: any), // Provider
Consumer: (null: any), // Consumer 为 context本身
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
context.Consumer = context;
return context;
}
当我们将Provider
以JSX
模式使用时,会创建对应的Fiber节点,也会进入beginWork
和completeWork
阶段。
通过createContext
方法可以知道Provider
和Consumer
为一个对象,首先会进入Fiber节点的创建。
Fiber节点的创建
createFiberFromTypeAndProps
方法会创建并返回Fiber节点,在这个方法里会判断Fiber节点的类型,Provider
和Consumer
都是对象,进入default
判断后会以$$typeof
来判断类型。
export function createFiberFromTypeAndProps(
// ...
): Fiber {
let fiberTag = IndeterminateComponent;
let resolvedType = type;
if (typeof type === 'function') {
// ...
} else if (typeof type === 'string') {
fiberTag = HostComponent;
} else {
getTag: switch (type) {
// ...
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
// tag为ContextProvider
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break getTag;
// tag为ContextConsumer
case REACT_CONTEXT_TYPE:
fiberTag = ContextConsumer;
break getTag;
// ...
}
}
}
}
}
const fiber = createFiber(fiberTag, pendingProps, key, mode);
// ...
}
beginWork阶段
beginWork
阶段会以Fiber节点的tag
判断进入哪一个方法,在Fiber节点创建的时候已经为Provider
和Consumer
设置了对应的tag
。
function beginWork(
// ...
): Fiber | null {
// ...
case ContextProvider:
return updateContextProvider(current, workInProgress, renderLanes);
case ContextConsumer:
return updateContextConsumer(current, workInProgress, renderLanes);
// ...
}
updateContextProvider
ContextProvider
类型会进入updateContextProvider
方法。
function updateContextProvider(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
/*
type = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
*/
const providerType: ReactProviderType<any> = workInProgress.type;
// 取出context
const context: ReactContext<any> = providerType._context;
const newProps = workInProgress.pendingProps;
const oldProps = workInProgress.memoizedProps;
// Provider所传入的value值
const newValue = newProps.value;
// 旧值入栈,赋新值
pushProvider(workInProgress, newValue);
// Update时
if (oldProps !== null) {
const oldValue = oldProps.value;
// value值无变化返回0,有变化返回MAX_SIGNED_31_BIT_INT
const changedBits = calculateChangedBits(context, newValue, oldValue);
if (changedBits === 0) {
// context没有变化
if (
oldProps.children === newProps.children &&
!hasLegacyContextChanged()
) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
}
} else {
// context的值变化了,所有消费了context的组件需要发起更新
propagateContextChange(workInProgress, context, changedBits, renderLanes);
}
}
const newChildren = newProps.children;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
pushProvider
pushProvider
方法是Context
的值变化的核心,它会将旧的值压入valueStack
,同时为Context
赋新值。
export function pushProvider<T>(providerFiber: Fiber, nextValue: T): void {
const context: ReactContext<T> = providerFiber.type._context;
// context旧值入栈
push(valueCursor, context._currentValue, providerFiber); // Stack标题内的push方法
// context的值设为新的值
context._currentValue = nextValue;
}
propagateContextChange
propagateContextChange
方法在Context
更新时使用,从当前Fiber节点开始遍历节点树,为使用了当前context
的子节点设置优先级。
设置优先级的目的是为了子节点在进入beginWork
阶段的时候不会进入bailout
的复用流程。
export function propagateContextChange(
workInProgress: Fiber,
context: ReactContext<mixed>,
changedBits: number,
renderLanes: Lanes,
): void {
let fiber = workInProgress.child;
if (fiber !== null) {
fiber.return = workInProgress;
}
while (fiber !== null) {
let nextFiber;
const list = fiber.dependencies;
if (list !== null) {
nextFiber = fiber.child;
let dependency = list.firstContext;
// 当前的子节点需要更新
while (dependency !== null) {
if (
dependency.context === context &&
(dependency.observedBits & changedBits) !== 0
) {
// 匹配,从该Fiber节点发起更新
// 如果是类组件使用了context,则添加一个forceUpdate的Update
if (fiber.tag === ClassComponent) {
const update = createUpdate(
NoTimestamp,
// renderLanes中最高优先级的lane
pickArbitraryLane(renderLanes),
);
update.tag = ForceUpdate;
enqueueUpdate(fiber, update);
}
// 后续fiber节点的遍历会判断节点有更新
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
// 设置lane标识有更新
scheduleWorkOnParentPath(fiber.return, renderLanes);
// 体现在prepareToReadContext方法里
list.lanes = mergeLanes(list.lanes, renderLanes);
break;
}
dependency = dependency.next;
}
} else if (fiber.tag === ContextProvider) {
// 子节点是ContextProvider返回null
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
} else if (
enableSuspenseServerRenderer &&
fiber.tag === DehydratedFragment
) {
// ...
} else {
nextFiber = fiber.child;
}
// 遍历兄弟节点
if (nextFiber !== null) {
nextFiber.return = fiber;
} else {
nextFiber = fiber;
while (nextFiber !== null) {
if (nextFiber === workInProgress) {
// 遍历到顶了
nextFiber = null;
break;
}
const sibling = nextFiber.sibling;
if (sibling !== null) {
sibling.return = nextFiber.return;
nextFiber = sibling;
break;
}
nextFiber = nextFiber.return;
}
}
fiber = nextFiber;
}
}
updateContextConsumer
Consumer
是使用Context
值的一种最基础的方式,ContextConsumer
类型会进入updateContextConsumer
方法。
function updateContextConsumer(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
// Consumer的type就是context本身
let context: ReactContext<any> = workInProgress.type;
const newProps = workInProgress.pendingProps;
const render = newProps.children;
// 全局变量的处理
prepareToReadContext(workInProgress, renderLanes);
// 读取context的值
const newValue = readContext(context, newProps.unstable_observedBits);
// render props的方式来将context值提供给子组件
let newChildren = render(newValue);
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
在这里与context
相关的是prepareToReadContext
方法和readContext
方法。
prepareToReadContext
export function prepareToReadContext(
workInProgress: Fiber,
renderLanes: Lanes,
): void {
// 把使用了context值的Fiber节点存在全局变量
currentlyRenderingFiber = workInProgress;
// 重置变量标识标识
lastContextDependency = null;
lastContextWithAllBitsObserved = null;
const dependencies = workInProgress.dependencies;
// context更新后才会进入判断
if (dependencies !== null) {
const firstContext = dependencies.firstContext;
if (firstContext !== null) {
if (includesSomeLane(dependencies.lanes, renderLanes)) {
// 设置didReceiveUpdate,函数组件内会根据这个判断
markWorkInProgressReceivedUpdate();
}
// 没懂
dependencies.firstContext = null;
}
}
}
readContext
readContext
方法不止在ContextConsumer
会用到,使用了contextType
的类组件和使用了useContext
的函数组件都会使用(后文),该方法不仅会返回context
的值,同时也记录了该Fiber节点使用了Context
,后续Context
改变会触发此节点的更新。
export function readContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T {
// 这一片逻辑与更新有关,实际值返回只在最后一行代码
if (lastContextWithAllBitsObserved === context) {
// 已经observe了
} else if (observedBits === false || observedBits === 0) {
// 不更新
} else {
let resolvedObservedBits; // Avoid deopting on observable arguments or heterogeneous types.
// 默认情况observedBits等于MAX_SIGNED_31_BIT_INT
if (
typeof observedBits !== 'number' ||
observedBits === MAX_SIGNED_31_BIT_INT
) {
// 标识Observed
lastContextWithAllBitsObserved = ((context: any): ReactContext<mixed>);
resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
} else {
resolvedObservedBits = observedBits;
}
const contextItem = {
context: ((context: any): ReactContext<mixed>),
observedBits: resolvedObservedBits,
next: null,
};
if (lastContextDependency === null) {
// 该Fiber节点的第一个依赖
lastContextDependency = contextItem;
// 记录该Fiber节点使用的Context
currentlyRenderingFiber.dependencies = {
lanes: NoLanes,
firstContext: contextItem,
responders: null,
};
} else {
// 已有其他的context依赖记录,用next连接
lastContextDependency = lastContextDependency.next = contextItem;
}
}
return context._currentValue;
}
completeWork阶段
completedWork
阶段会将调用popProvider
将当前valueStack
栈中的旧值弹出并赋值给ContextProvider
。
function completeWork(
// ...
): Fiber | null {
// ...
case ContextProvider:
// Pop provider fiber
popProvider(workInProgress);
return null;
// ...
}
popProvider
export function popProvider(providerFiber: Fiber): void {
const currentValue = valueCursor.current;
pop(valueCursor, providerFiber);
const context: ReactContext<any> = providerFiber.type._context;
// 改变context的值为旧值
context._currentValue = currentValue;
}
为什么会赋值为旧值呢?如以下情况。
const Context = React.createContext(-1);
<Context.Provider value={0}>
<Context.Provider value={1}>
<A/> // 1
</Context.Provider>
<B/> // 0
</Context.Provider>
当beginWork
执行到value = 0
到ContextPrivder
时,将默认值-1
压入栈,同时赋予新值0
,接下来执行value = 1
的ContextProvider
,将旧值0
压入栈,同时赋予新值1
,这时候A
组件读取的值为1
。
接下来执行completeWork
阶段,当到value = 1
的ContextProvider
时,将旧值0
从栈弹出,同时赋予旧值。
接下来在B
组件的beginWork
阶段,读取的ContextProvider
的值才会为正确的0
,最后依次执行completeWork
阶段,将ContextProvider
值还原为默认值-1
。
为了保证这样的层级关系,所以需要保留旧值来还原。
子级如何判断有更新?
在Fiber树构建流程中,如果当前更新的renderLanes
不包含WorkInProgress
的lane
,就会进入bailoutOnAlreadyFinishedWork
方法,就不会走更新流程了。
所以在propagateContextChange
方法里,会对使用了Context
对子级节点设置lane
,确保不会进入bailoutOnAlreadyFinishedWork
方法。
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const updateLanes = workInProgress.lanes;
// renderLans不包含节点lane,就会进入bailout方法
if (!includesSomeLane(renderLanes, updateLanes)) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// update...
}
类组件和函数组件使用Context
类组件使用方式是利用contextType
,函数组件则是简单的useContext
。
类组件
类组件流程和ContextConsumer
是一样的,同样先调用prepareToReadContext
重置全局变量,在通过调用readContext
获取context
并添加依赖关系。
function updateClassComponent(
// ...
) {
// 重置全局变量
prepareToReadContext(workInProgress, renderLanes);
// 以下简化了代码...
// 类实例
const instance = workInProgress.stateNode;
const contextType = ctor.contextType;
if (typeof contextType === 'object' && contextType !== null) {
// 读取context挂在实例的context上
instance.context = readContext(contextType);
}
// ...
}
函数组件
函数组件同样是需要先调用prepareToReadContext
重置全局变量,再调用useContext
来获取值。
function updateFunctionComponent(
// ...
) {
prepareToReadContext(workInProgress, renderLanes);
// 执行函数组件,来获取children,有使用useContext就会执行readContext
let nextChildren = renderWithHooks(
// ...
);
// 在prepareToReadContext判断后didReceiveUpdate为true,不会进入bailout
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// ...
}
而useContext
本质上就是readContext
,和其他Hooks
非常不一样。
const dispatcher: Dispatcher = {
useContext: readContext,
}
export function useContext<T>(
Context: ReactContext<T>,
unstable_observedBits: number | boolean | void,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useContext(Context, unstable_observedBits);
}
总结
文章逻辑写的有点狗屁不通,难受啊。
-
当Fiber节点为
ContextProvider
时,会将旧值压入栈,并为Context
赋予新值,当有更新时,会遍历子级节点,找到有依赖关系的Fiber节点,标识它们需要更新。 -
当Fiber节点需要使用
Context
时,会先调用prepareToReadContext
方法来设置全局变量,读取Context
需要调用readContext
方法,该方法同时会记录此节点与Context
的依赖关系。 -
类组件和函数组件调用
Context
的逻辑实际上和ContextConsumer
是一样的。
在这里还发现一个有意思的东西,createContext
的第二个参数calculateChangedBits
,在文档上是没有使用的,看逻辑应该是和是否需要更新节点有关,原来并不是Context
一改变,所有使用了的节点都需要更新啊!