useContext
概念
useContext 用于在函数式组件中访问上下文(Context)的值。
Context 是一种在 react 不同组件(跨层级,例如父子组件,父孙组件)之间共享,传递数据的机制。
useContext 的参数是由 createContext 创建,或者是父级上下文 context 传递的,通过 Context.Provider 包裹的组件,才能通过 useContext 获取对应的值。可以理解为 useContext 代替之前 Context.Consumer 来获取 Provider 中保存的 value 值。
// 内部数据共享层
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
currentDispatcher,
};
const currentDispatcher = {
current: null,
};
我们在使用 hook 时,react 在内部通过 currentDispatcher.current 赋予不同的函数来处理不同阶段的调用,判断 hook 是否在函数组件内部调用.
用法
原理


根据 useContext 这个函数的功能可见,其实它的职责只是获取 Context 的值,无论是在 mount 阶段还是 update 阶段。
相比于其他 hook 函数在不同阶段需要执行的功能不一致,useContext 在初始化和更新阶段的功能是一致的:获取 Context 中保存的值。
currentlyRenderingFiber 是一个全局变量,保存当前正在处理的 fiber 节点,因为 Context 功能存在于函数组件中,所以当前处理的节点必然是一个函数组件类型 fiber
createContext
context 与 Provider 相互引用,context 对象中保存原始值,作为函数返回值导出。而 Provider 作为一个标签节点使用,同时也可以通过_context 属性获取到保存在 context 对象中的值。
$$typeof 为节点标记。例如普通的 dom 节点标记为 REACT_ELEMENT_TYPE。
在生产 fiber 阶段时,会根据$$typeof 这个节点标记为 fiber 节点生成不同的 tag
Context 的逻辑
需要实现两部分内容:
对 ContextProvider 类型 FiberNode 的支持
Context 逻辑的实现
由于我们在 createContext 函数中已经导出了一个包含有 Provider 对象返回值。所以我们在使用 GuaContext.Provider 作为标签使用时,这个标签在编译后的已经是一个$$typeof 类型为 REACT_PROVIDER_TYPE 的 element 对象了,所以在生成 fiber 节点时这个标签的 tag 被标记为 ContextProvider。
数据保存与 context 嵌套
既然已经通过 createContext 函数生成了一个 Context 对象,那么在处理 ContextProvider 类型的节点时,首要任务就是将新的值更新,支持 Context._currentValue 的变化。
新的值是通过 value 属性传递,我们可以通过 fiber 节点的 pendingProps 属性获取新的值。

在创建 fiber 节点的过程中,遇到 ContextProvider 标记的节点代表当前是一个 Provider 标签,更新与当前节点对应的 Context 对象的值。当子孙节点使用 useContext 函数时传入同一个 Context 对象即可获取最新值。
如果整棵 fiber 树中之存在一个 Context,可以直接更新 Context 对象,但是在实际的情况中可能会有多层嵌套:
所以我们使用栈的形式来存储值,由于整个 render 阶段的处理过程是先深度遍历,到达最深处节点后回溯,直到根节点。beginWork 由上至下,completeWork 由下至上。
所以在 beginWork 流程中入栈,completeWork 流程出栈,可以满足每一层的对应关系。

定义两个全局变量,prevContextValue 代表当前正在处理的 context 对象,prevContextValueStack 保存 context 对象栈
completeWork 回溯的过程与 beginWork 类似,同样也是通过 tag 对不同的 fiber 类型的节点调用不同的处理逻辑。
由于当前 fiber 将要回溯到父级,所以更新为父级的值。同时 prevContextValueStack 栈出栈。
最后更新于
这有帮助吗?