第十五章: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组件的包裹就可以直接使用了,只不过就是没有太多意义。

相关推荐
蟾宫曲4 小时前
在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)
前端·npm·vue3·vite·element-plus·计时器
秋雨凉人心4 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
liuxin334455664 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
qq13267029404 小时前
运行Zr.Admin项目(前端)
前端·vue2·zradmin前端·zradmin vue·运行zradmin·vue2版本zradmin
魏时烟5 小时前
css文字折行以及双端对齐实现方式
前端·css
2401_882726486 小时前
低代码配置式组态软件-BY组态
前端·物联网·低代码·前端框架·编辑器·web
web130933203986 小时前
ctfshow-web入门-文件包含(web82-web86)条件竞争实现session会话文件包含
前端·github
胡西风_foxww6 小时前
【ES6复习笔记】迭代器(10)
前端·笔记·迭代器·es6·iterator
前端没钱6 小时前
探索 ES6 基础:开启 JavaScript 新篇章
前端·javascript·es6
m0_748255267 小时前
vue3导入excel并解析excel数据渲染到表格中,纯前端实现。
前端·excel