「这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战」
回忆 react 的使用文档,它还介绍了 context 这种跨组件传递 prop 的通信方式,它的实现原理是什么?正好我们在 updateFunctionComponent 函数中看到了 getUnmaskedContext 等与 context 通信相关的函数,今天就来分析一下这些函数的源码。
context 特征
在研究 context 源码之前,我们回忆一下官网对它的介绍。
首先,context 提供了一种不需要手动将 props 在嵌套组件中层层传递就可以在深层子组件使用该 props 的方法。
使用 context 的方法可以分成以下几个步骤:
- 通过
React.createContext (value)方法创建 Context 对象 - 用 Context.Provider 组件包裹可能需要用到该 context的后代组件,给 Provider 传递 value 属性
- 在需要使用该 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
下期预告
至于引用这些函数的位置和其中的上下文逻辑,将在下一篇文章中讲解。