简述
在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一改变,所有使用了的节点都需要更新啊!