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,
};
render流程

我们在使用 hook​ 时,react 在内部通过 ​currentDispatcher.current​ 赋予不同的函数来处理不同阶段的调用,判断 hook 是否在函数组件内部调用.

用法

原理

context 复习
hooks 环境下的 content

根据 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 更新

在创建 fiber​ 节点的过程中,遇到 ContextProvider​ 标记的节点代表当前是一个 Provider​ 标签,更新与当前节点对应的 Context​ 对象的值。当子孙节点使用 useContext​ 函数时传入同一个 Context​ 对象即可获取最新值。

如果整棵 fiber​ 树中之存在一个 Context​,可以直接更新 Context​ 对象,但是在实际的情况中可能会有多层嵌套:

所以我们使用栈的形式来存储值,由于整个 render​ 阶段的处理过程是先深度遍历,到达最深处节点后回溯,直到根节点。beginWork​ 由上至下,completeWork​ 由下至上。

所以在 beginWork​ 流程中入栈,completeWork​ 流程出栈,可以满足每一层的对应关系。

alt text

定义两个全局变量,prevContextValue​ 代表当前正在处理的 context​ 对象,prevContextValueStack​ 保存 context​ 对象栈

​completeWork​ 回溯的过程与 beginWork​ 类似,同样也是通过 tag​ 对不同的 fiber​ 类型的节点调用不同的处理逻辑。

由于当前 fiber​ 将要回溯到父级,所以更新为父级的值。同时 prevContextValueStack​ 栈出栈。

最后更新于

这有帮助吗?