第十五章:useContext源码解析

前言

useContext是react很基础的一个hook api,在一些比较大型,复杂的项目中常用,通过使用 useContext,你可以更轻松地在组件之间共享状态,避免了通过 props 层层传递数据的麻烦。这在大型应用程序中特别有用,因为它提供了一种更直观的全局状态管理方式。
useContext是一个hook,但是没有单独使用场景,是需要搭配createContext配合使用,所以在讲解useContext的时候也会顺带一些createContext和react构建过程一起讲解。

源码解析

js 复制代码
const value = useContext(Context)

按照常规流程,调用HooksDispatcherOnMountInDEV.useContext 的方法,前置check检查方法,核心方法readContext
useContext不会创建hook对象,首先直接会读取值

js 复制代码
const value =  context._currentValue ;
const contextItem = {
  context: context,
  memoizedValue: value,
  next: null
};
fiber.dependencies = {
  lanes: 0,
  firstContext: contextItem
}
return value;

firstContext是一个单向链表,后续的useContext 会往next挂载;

返回value值,原代码很简单,还需要配合createContext 使用;

mount阶段和update阶段的useContext的源码是一样的,因为只有读值,所有不存在差异化。

createContext源码解读

js 复制代码
const SomeContext = createContext(defaultValue)

createContext主要就是创建一个context上下文对象,

js 复制代码
const context = {
    $$typeof: REACT_CONTEXT_TYPE, //这个一个组件标签,在遍历标签的时候找到对应的方法
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    _threadCount: 0,
    Provider: null,
    Consumer: null,
    _defaultValue: null,
    _globalName: null
};
context.Provider = {
   $$typeof: REACT_PROVIDER_TYPE,
   _context: context
};
const Consumer = {
   $$typeof: REACT_CONTEXT_TYPE,
   _context: context,
};
Object.defineProperties(Consumer, {
      Provider: {
        get() {
          console.error(
              'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
                'a future major release. Did you mean to render <Context.Provider> instead?',
          );
          return context.Provider;
        },
        set(_Provider) {
          context.Provider = _Provider;
        },
      },
      _currentValue: {
        get() {
          return context._currentValue;
        },
        set(_currentValue) {
          context._currentValue = _currentValue;
        },
      },
      _currentValue2: {
        get() {
          return context._currentValue2;
        },
        set(_currentValue2) {
          context._currentValue2 = _currentValue2;
        },
      },
      _threadCount: {
        get() {
          return context._threadCount;
        },
        set(_threadCount) {
          context._threadCount = _threadCount;
        },
      },
      Consumer: {
        get() {
          console.error(
              'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
                'a future major release. Did you mean to render <Context.Consumer> instead?',
          );
          
          return context.Consumer;
        },
      },
      displayName: {
        get() {
          return context.displayName;
        },
        set(displayName) {
            console.warn(
              'Setting `displayName` on Context.Consumer has no effect. ' +
                "You should set it directly on the context with Context.displayName = '%s'.",
              displayName,
            );
        },
      },
    });
context.Consumer = Consumer;
context._currentRenderer = null;
context._currentRenderer2 = null;
return context;

上面是通过createContext创建的对象,可以看出实际上导出三个组件,context可以作为一个组件,context.Consumer和context.Provider三个组件,context和context.Consumer是同一种类型组件,但是不推荐直接使用context写法,而且react内部也会报error,

首先看一下context.Consumer的写法,

js 复制代码
<SomeContext.Consumer>
{
    (context)=>(
        <ChildComponent {...context} />
    )
}
</SomeContext.Consumer>

提一点,SomeContext的作为组件的时候,首字母必须大写,这样react认为是自定义组件进行解析。

在遍历到SomeContext.Consumer组件中,子组件必须是个函数,也是调用readContext(context)这个是react内部直接执行,就是Consumer自己执行了useContext方法了。

执行children函数

js 复制代码
const newChildren = readContext(context);
const newChildren = children(newValue);

Consumer组件只有消费能力,是没办法作为生产者。

Consumer组件在class组件中比较常用,函数式组件中还是常用Provider组件,看一下Provider组件的写法:

js 复制代码
<SomeContext.Provider value={newValue}>
    <ChildContext onclick={onclick}  />
</SomeContext.Provider>

在beginWork阶段,Provider的代码:

js 复制代码
// providerType._context === context.Provider._context
providerType._context._currentValue = props.value;

将Provider的value属性更新到context的值,继续遍历子节点。

而在completeUnitOfWork 阶段,就是遍历完成阶段,会将defaultValue重新赋值给context._currentValue ,所以在update阶段中,在Provider的父组件中是没办法获取更新之后的value值。

所以在子组件中使用useContext获取的value值是Provider更新之后的value。

总结

context部分react会在类组件中或者函数组件中都使用,这章只是讲解函数式组件的写法;
useContextcreateContext为了解决深层次组件的传值的问题,本质上就是创建一个全局对象,在对象中保留值和组件标识符。

但是组件和hook都没有可以触发更新的方法,所以需要和其他方法或者hook搭配使用,解决可以更新。

补充

context会有组件标识,是不是也可以不使用context的组件也可以使用useContext方法,

js 复制代码
const ThemeContext = createContext({context:'createContext'});
// child
function Child(){
    const context useContext(ThemeContext)
    return <div>{context.context}</div>
}

child组件不需要有context组件的包裹就可以直接使用了,只不过就是没有太多意义。

相关推荐
学习使我快乐0125 分钟前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio199525 分钟前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈1 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水2 小时前
简洁之道 - React Hook Form
前端
正小安4 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch6 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光6 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   6 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   6 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web6 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery