React context源码

更新于 阅读 20

React context

大纲:

  1. Context 的作用,context是一个对象
  2. Context.Provider的执行过程:pushProvider,propagateContextChange,(mount、update阶段的不同),(通过多个不同context来举例,画图说明)
  3. Context.Consumer的执行过程: prepareToReadContext,
  4. useContext的执行过程

本文是关于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嵌套使用的例子,

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源码

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; }

调和阶段

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阶段恢复旧值,并将游标指向了父级context 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:

  1. 遍历fiber的后代diber,如果存在dependencies,就从dependencies中查找是否消费了workInProgress对应的context
  2. 如果当前后代fiber消费了workInProgress对应的context,在fiber及其dependencies上添加renderLanes,同时从当前fiber开始给父级fiber的childLanes添加renderLanes
function propagateContextChange_eager<T>( workInProgress: 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

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来判断组件是否需要更新,并且重置 dependencies.firstContext = null; prepareToReadContext(workInProgress, renderLanes); // 读取value,并重新设置dependencies.firstContext 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; }