React context 源码

这篇文章发表于 阅读 3

本文是关于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> ); }

效果如下,点击修改按钮,count1count2的值互不干扰。

Context对象创建后,ProviderConsumer可以直接在从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来确定,比如上面createContextProvider$$typeofREACT_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)

  1. 遍历fiber的后代diber,如果存在dependencies,就从dependencies中查找是否消费了跟workInProgress对应的context。
  2. 如果当前后代fiber消费了workInProgress对应的context,在fiber及其dependencies上添加renderLanes,同时从当前fiber开始给父级fiber的childLanes添加renderLanes,一直到workInProgress为止,并且dependencies的循环立即结束。
  3. 如果当前后代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

  1. 通过prepareToReadContext函数确定要组件是否需要更新。通过workInProgress的dependencies.lanes上是否包含renderLanes来判断组件是否需要更新(设置didReceiveUpdate = true),并且重置 dependencies.firstContext = null;
  2. 获取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做了哪些工作呢?

  1. updateContextProvider中通过pushProvider,将context的旧值保存起来,然后设置新值。也就是第二次使用ContextProvider时,会将第一次ContextProvider的value存起来。
  2. 在fiber completeWork阶段会将context上一次设置的值恢复出来。也就是第二次ContextProvidercompleteWork阶段会把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--; }