React 源码系列:context 原理

·  阅读 632
React 源码系列:context 原理

「这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战

回忆 react 的使用文档,它还介绍了 context 这种跨组件传递 prop 的通信方式,它的实现原理是什么?正好我们在 updateFunctionComponent 函数中看到了 getUnmaskedContext 等与 context 通信相关的函数,今天就来分析一下这些函数的源码。

context 特征

在研究 context 源码之前,我们回忆一下官网对它的介绍。

首先,context 提供了一种不需要手动将 props 在嵌套组件中层层传递就可以在深层子组件使用该 props 的方法。

使用 context 的方法可以分成以下几个步骤:

  1. 通过 React.createContext (value) 方法创建 Context 对象
  2. 用 Context.Provider 组件包裹可能需要用到该 context的后代组件,给 Provider 传递 value 属性
  3. 在需要使用该 Context 的组件中外面包一层 Context.Consumer ,或者使用钩子 useContext 消费该 Context 的数据

相关源码

updateFunctionComponent 函数中用到了getUnmaskedContext等函数,这些函数是定义在 react 代码仓库的packages\react-reconciler\src\ReactFiberContext.new.js中的,这次阅读源码我们先理解这个文件里的所有函数或全局变量,再找出引用它们的地方,理解调用的逻辑,从而得到对 context 原理的整体认识。

函数作用

ReactFiberContext.new.js 模块暴露的函数如下:

 export {
   getUnmaskedContext,
   cacheContext,
   getMaskedContext,
   hasContextChanged,
   popContext,
   popTopLevelContextObject,
   pushTopLevelContextObject,
   processChildContext,
   isContextProvider,
   pushContextProvider,
   invalidateContextProvider,
   findCurrentUnmaskedContext,
 }
 ​
复制代码

getUnmaskedContext

 function getUnmaskedContext (
   workInProgress: Fiber,
   Component: Function,
   didPushOwnContextIfProvider: boolean,
 ): Object {}
复制代码

获取 Component 能够使用的所有 Context。 可用的 Context 保存在一个栈中,如果 Component 是一个 Context.Provider,获取的是栈顶的第二个 context,因为 Context.Provider 也会把自己提供的 context 压入栈中,但是 Provider 不应该使用自身提供的 Context,它能使用的 context 至少是来自父组件的。

cacheContext

 function cacheContext (
   workInProgress: Fiber,
   unmaskedContext: Object,
   maskedContext: Object,
 ): void {}
复制代码

分别将 maskedContext 和 unmaskedContext 缓存到 workInProgress.stateNode 的两个名字很长的字段中。

getMaskedContext

 function getMaskedContext(
   workInProgress: Fiber,
   unmaskedContext: Object,
 ): Object {
 }
 ​
复制代码

从 unmaskedContext 中挑选被 workInProgress 消费的 context。从 workInProgress.type.contextTypes 获取 contextTypes,即被消费的 context 类型,然后从 unmaskedContext 挑选 key 在 contextTypes 中的 context,与对应的key 组成一个新的对象,即需要返回的 maskedContext。为了避免每次调用该函数都要新建 maskedContext,使用 cacheContext 函数缓存结果,除非 unmaskedContext 更新,否则不会重新创建 maskedContext。

hasContextChanged

检查 context 是否发生变化,这是通过检查 didPerformWorkStackCursor.current 来实现的。

popContext/popTopLevelContextObject

从 valueStack 值栈中执行两次 pop 操作,分别 pop 到 didPerformWorkStackCursor 和 contextStackCursor 上。

pushTopLevelContextObject

与前面的 api 相反,依次从 contextStackCursor 和 didPerformWorkStackCursor 取元素 push 到值栈 valueStack 上。

processChildContext

 function processChildContext (
   fiber: Fiber,
   type: any,
   parentContext: Object,
 ): Object {}
复制代码

合并 fiber.stateNode.getChildContext() 和 parentContext,返回合并结果。

isContextProvider

 function isContextProvider (type: Function): boolean {}
复制代码

type 是组件函数,通过检查 type.childContextTypes 是否存在来识别该组件是否是一个 Provider。

pushContextProvider

 function pushContextProvider (workInProgress: Fiber): boolean {}
复制代码

workInProgress 是一个 Provider,将 workInProgress 的缓存的合并后的 context 压入contextStackCursor,在此之前用 previousContext 记录旧 contextStackCursort,previousContext 压入栈;将 didPerformWorkStackCursor.current, 压入栈(与前面的context 共用一个 valueStack)

invalidateContextProvider

 function invalidateContextProvider (
   workInProgress: Fiber,
   type: any,
   didChange: boolean,
 ): void {}
复制代码

如果 didChange 是 true,则调 processChildContext 得到父组件和自身的 context 的合并结果,并缓存起来(赋值给 __reactInternalMemoizedMergedChildContext),更新contextStackCursor 和 didPerformWorkStackCursor

findCurrentUnmaskedContext

 function findCurrentUnmaskedContext (fiber: Fiber): Object {}
复制代码

获取距离 fiber 最近的外层 Provider 提供的 context

下期预告

至于引用这些函数的位置和其中的上下文逻辑,将在下一篇文章中讲解。

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改