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