React context 源码
本文是关于Context
源码的笔记,关于Context
的使用,查看官方文档。
下面的创建了一个countContext
,点击按钮时count值 + 1;
const CountContext = createContext(); function App() { const [count, setCount] = useState(0); const handleClick = () => { setCount((count) => count + 1); } return ( <div className="p-12"> <CountContext.Provider value={count}> <CountContext.Consumer> { (value) => <div className="mb-4">count: {value}</div> } </CountContext.Consumer> <button className="px-4 py-2 font-semibold text-sm bg-cyan-500 text-white rounded-full shadow-sm" onClick={handleClick}>修改count</button> </CountContext.Provider> </div> ); }
在线运行效果
再举一个context嵌套使用的例子,其中CountContext.Provider
被嵌套使用了两次,CountContext.Consumer
获取到的value是最近的CountContext.Provider
提供。
const CountContext = createContext(); function App() { const [count1, setCount1] = useState(0); const [count2, setCount2] = useState(0); const handleClick1 = () => { setCount1((count) => count + 1); } const handleClick2 = () => { setCount2((count) => count + 1); } return ( <div className="p-12"> <CountContext.Provider value={count1}> <CountContext.Consumer> { (value) => <div> <div className="mb-4">count1:{value}</div> <CountContext.Provider value={count2}> <CountContext.Consumer> { (value) => <div> <div className="mb-4">count2:{value}</div> </div> } </CountContext.Consumer> </CountContext.Provider> </div> } </CountContext.Consumer> </CountContext.Provider> <button className="px-4 py-2 font-semibold text-sm bg-cyan-500 text-white rounded-full shadow-sm mr-4" onClick={handleClick1}>修改count1</button> <button className="px-4 py-2 font-semibold text-sm bg-cyan-500 text-white rounded-full shadow-sm" onClick={handleClick2}>修改count2</button> </div> ); }
效果如下,点击修改按钮,count1
、count2
的值互不干扰。
Context
对象创建后,Provider
和Consumer
可以直接在从context中读取value,那嵌套使用时是怎么做到互不干扰的呢,下面通过源码来了解。
createContext源码
createContext
用于创建context
对象,同时通过_currentValue/_currentValue2
保存Provider
设置的value。
export function createContext<T>(defaultValue: T): ReactContext<T> { const context: ReactContext<T> = { $$typeof: REACT_CONTEXT_TYPE, _currentValue: defaultValue, _currentValue2: defaultValue, _threadCount: 0, Provider: (null: any), Consumer: (null: any), }; if (enableRenderableContext) { context.Provider = context; context.Consumer = { $$typeof: REACT_CONSUMER_TYPE, _context: context, }; } else { // 设置Provider (context: any).Provider = { $$typeof: REACT_PROVIDER_TYPE, _context: context, }; if (__DEV__) { // ... } else { // 设置Consumer (context: any).Consumer = context; } } return context; }
调和阶段
在调和阶段,根据fiber的tag来确定执行逻辑,tag
是创建fiber时根据$$typeof
来确定,比如上面createContext
中Provider
的$$typeof
是REACT_PROVIDER_TYPE
,在调和过程中,创建fiber时就根据组件的$$typeof
来设置tag
function beginWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ): Fiber | null { // ... switch (workInProgress.tag) { // ... case ContextProvider: return updateContextProvider(current, workInProgress, renderLanes); case ContextConsumer: return updateContextConsumer(current, workInProgress, renderLanes); } }
updateContextProvider
通过pushProvider
为context设置新值,同时将context以前的值存起来,然后在当前fiber调和完成后context的值就恢复成旧值。
如果是在更新阶段,value发生了变化,propagateContextChange
就会遍历后代节点,通过fiber.dependencies
来查找是否消费了这个context,如果消费了就在fiber上添加lanes,同时dependencies
上也添加lanes,后代组件可以通过dependencies.lanes
来确实是否需要更新(可以在updateContextConsumer
函数中看到调用了prepareToReadContext
函数,prepareToReadContext
中通过dependencies
来标记组件是否需要更新)。
function updateContextProvider( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ) { let context: ReactContext<any>; if (enableRenderableContext) { context = workInProgress.type; } else { context = workInProgress.type._context; } const newProps = workInProgress.pendingProps; const oldProps = workInProgress.memoizedProps; const newValue = newProps.value; // ... // 保存context以前的值,并给context设置新的值,在completeWork阶段恢复成以前的值 pushProvider(workInProgress, context, newValue); if (enableLazyContextPropagation) { } else { if (oldProps !== null) { const oldValue = oldProps.value; if (is(oldValue, newValue)) { // 如果value没有变化,同时children没变,直接复用 if ( oldProps.children === newProps.children && !hasLegacyContextChanged() ) { return bailoutOnAlreadyFinishedWork( current, workInProgress, renderLanes, ); } } else { // value发生变化,找到 consumer 并安排更新 propagateContextChange(workInProgress, context, renderLanes); } } }
propagateContextChange_eager
给消费了context的后代节点添加lanes:(其中workInProgress
参数就是Provider
组件的fiber)
- 遍历fiber的后代diber,如果存在
dependencies
,就从dependencies
中查找是否消费了跟workInProgress
对应的context。 - 如果当前后代fiber消费了
workInProgress
对应的context,在fiber及其dependencies
上添加renderLanes,同时从当前fiber开始给父级fiber的childLanes添加renderLanes,一直到workInProgress
为止,并且dependencies
的循环立即结束。 - 如果当前后代fiber也是
Provider
组件,并且type跟workInProgress
相同时(表示是同一个context的provider
),停止向下查找;因为下面的Consumer
消费的是最近的Provider
。
function propagateContextChange_eager<T>( workInProgress: Fiber, // 就是Provider对应的fiber context: ReactContext<T>, renderLanes: Lanes, ): void { // Only used by eager implementation if (enableLazyContextPropagation) { return; } let 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) { let nextFiber; // Visit this fiber. // fiber.dependencies 只有在组件mount后才会存在,所以在mount阶段下面的逻辑不会执行 const list = fiber.dependencies; if (list !== null) { nextFiber = fiber.child; // 遍历fiber消费的context,如果类型相同,就表示该fiber需要更新,所以会在fiber.lanes、fiber.dependencies.lanes上添加renderLanes,表示该组件需要更新 let dependency = list.firstContext; while (dependency !== null) { // Check if the context matches. // 检查fiber是否消费了context if (dependency.context === context) { // 判断组件类型 if (fiber.tag === ClassComponent) { // 如果是class组件,会创建一个update并标记为forceUpdate const lane = pickArbitraryLane(renderLanes); const update = createUpdate(lane); update.tag = ForceUpdate; // Inlined `enqueueUpdate` to remove interleaved update check const updateQueue = fiber.updateQueue; if (updateQueue === null) { // Only occurs if the fiber has been unmounted. } else { const sharedQueue: SharedQueue<any> = (updateQueue: any).shared; const 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 fiber.lanes = mergeLanes(fiber.lanes, renderLanes); const alternate = fiber.alternate; if (alternate !== null) { alternate.lanes = mergeLanes(alternate.lanes, renderLanes); } // 从当前fiber开始,给所有父级fiber的childLanes添加renderLanes,一直到workInProgress为止 scheduleContextWorkOnParentPath( fiber.return, renderLanes, workInProgress, ); // 在fiber.dependencies.lanes上添加renderLanes list.lanes = mergeLanes(list.lanes, renderLanes); // 一旦fiber节点上找到了一个更新了的conetxt,则表示该fiber需要更新,就不需要继续遍历其他的dependency了(因为组件本来已经标记为更新了,继续遍历没有意义) break; } dependency = dependency.next; } } else if (fiber.tag === ContextProvider) { // 如果后代组件的类型的是Provider,并且与当前workInProgress的类型相同就停止向下查找(workInProgress的类型是ContextProvider, 如果后代组件也是ContextProvider并且他们的context还相同,说明出现了同一个ContextProvider的嵌套使用,停止向下遍历) nextFiber = fiber.type === workInProgress.type ? null : fiber.child; } else if (fiber.tag === DehydratedFragment) { // 。。。 } 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; } const 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; } }
updateContextConsumer
- 通过
prepareToReadContext
函数确定要组件是否需要更新。通过workInProgress的dependencies.lanes上是否包含renderLanes来判断组件是否需要更新(设置didReceiveUpdate = true
),并且重置 dependencies.firstContext = null; - 获取value,并设置
dependencies
数据;
function updateContextConsumer( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ) { let context: ReactContext<any>; if (enableRenderableContext) { const consumerType: ReactConsumerType<any> = workInProgress.type; context = consumerType._context; } else { context = workInProgress.type; } const newProps = workInProgress.pendingProps; const render = newProps.children; // ... // 通过workInProgress的dependencies.lanes上是否包含renderLanes来判断组件是否需要更新(didReceiveUpdate = true),并且重置 dependencies.firstContext = null; prepareToReadContext(workInProgress, renderLanes); // 读取value,并重新设置 dependencies const newValue = readContext(context); if (enableSchedulingProfiler) { markComponentRenderStarted(workInProgress); } let newChildren; if (__DEV__) { // ... } else { // 渲染Consumer的子节点 newChildren = render(newValue); } if (enableSchedulingProfiler) { markComponentRenderStopped(); } // React DevTools reads this flag. workInProgress.flags |= PerformedWork; reconcileChildren(current, workInProgress, newChildren, renderLanes); return workInProgress.child; }
useContext
useContext就是readContext函数,在updateContextConsumer
中也调用了readContext
,最后逻辑在readContextForConsumer
中实现
const HooksDispatcherOnMount: Dispatcher = { // ... useContext: readContext, // ... };
readContextForConsumer
返回context的value,同时给fiber.dependencies设置值,其中firstContext
是一个链表,因为在函数组件中,存在使用useContext
消费多个context的情况,所以使用lastContextDependency
记录最近的contextItem,其他的contextItem通过next添加在后面
function readContextForConsumer<T>( consumer: Fiber | null, context: ReactContext<T>, ): T { const value = isPrimaryRenderer ? context._currentValue : context._currentValue2; if (lastFullyObservedContext === context) { // Nothing to do. We already observe everything in this context. } else { const contextItem = { context: ((context: any): ReactContext<mixed>), memoizedValue: value, next: null, }; if (lastContextDependency === null) { // ... // This is the first dependency for this component. Create a new list. lastContextDependency = contextItem; // 关于dependencies的理解: // 1. 通过useContext获取值时,会在fiber上形成一个链表,组件可能通过useContext获取多个context的value,所以会在 propagateContextChange_eager 函数中遍历fiber.dependencies,如果context相同就添加renderLanes在fiber上, // 2. 通过Cusumer获取值时,也会有dependencies但是只有一个节点 consumer.dependencies = { lanes: NoLanes, firstContext: contextItem, }; if (enableLazyContextPropagation) { consumer.flags |= NeedsPropagation; } } else { // 追加contextItem lastContextDependency = lastContextDependency.next = contextItem; } } // 返回value return value; }
同一个ContextProvider多次嵌套使用如何拿到正确的值
上面第二个例子中,同一个ContextProvider嵌套使用了两次,并且拿到了正确的值,React做了哪些工作呢?
- 在
updateContextProvider
中通过pushProvider
,将context的旧值保存起来,然后设置新值。也就是第二次使用ContextProvider
时,会将第一次ContextProvider
的value存起来。 - 在fiber
completeWork
阶段会将context上一次设置的值恢复出来。也就是第二次ContextProvider
在completeWork
阶段会把value恢复成第一次设置的value值(通过popProvider
函数恢复)。
pushProvider
假设当前fiber是Provider第二次使用的fiber。
这里将context._currentValue2
(也就是第一次的值)推入队列,然后设置为新值(第二次的value)
export function pushProvider<T>( providerFiber: Fiber, context: ReactContext<T>, nextValue: T, ): void { if (isPrimaryRenderer) { // ... } else { // context上一个context.provider的value, 在completeWork阶段通过popProvider恢复context的value,并将valueCursor指向最近的父级context.Provider组件 // valueCursor.current也是上一个provider的value push(valueCursor, context._currentValue2, providerFiber); // 给context设置新值 context._currentValue2 = nextValue; } }
- valueStack: 保存context的value的栈,不包括当前Provider设置的值;这里就保存到第一次的值。
- valueCursor:指向前一次Provider的值。这里就指向第一次的值。用于
popProvider
中恢复上一次的值。
function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void { index++; valueStack[index] = cursor.current; // cursor.current 为context的value cursor.current = value; }
popProvider
假设当前fiber是Provider第二次使用的fiber。
该方法在completeWork
阶段恢复context的值为第一次设置的值,因为valueCursor.current
的值就为第一次的值,所以这里直接设置给context._currentValue2
,恢复context的值。
export function popProvider( context: ReactContext<any>, providerFiber: Fiber, ): void { const currentValue = valueCursor.current; if (isPrimaryRenderer) { // ... } else { context._currentValue2 = currentValue; } pop(valueCursor, providerFiber); }
同时通过pop
函数将valueCursor
设置为上上次的值,并删除valueStack
中第二次的数据
function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void { if (index < 0) { // ... } cursor.current = valueStack[index]; valueStack[index] = null; index--; }