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

相关推荐
失败又激情的man5 分钟前
python爬虫之数据存储
前端·数据库·python
互联网搬砖老肖6 分钟前
Web 架构之 API 安全防护:防刷、防爬、防泄漏
前端·安全·架构
小声读源码41 分钟前
【技巧】dify前端源代码修改第一弹-增加tab页
前端·pnpm·next.js·dify
假客套1 小时前
2025 后端自学UNIAPP【项目实战:旅游项目】7、景点详情页面【完结】
前端·uni-app·旅游
程序员小张丶1 小时前
基于React Native开发HarmonyOS 5.0主题应用技术方案
javascript·react native·react.js·主题·harmonyos5.0
Captaincc1 小时前
Ilya 现身多大毕业演讲:AI 会完成我们能做的一切
前端·ai编程
teeeeeeemo1 小时前
Vue数据响应式原理解析
前端·javascript·vue.js·笔记·前端框架·vue
Sahas10191 小时前
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is not explicitly defined.
前端·javascript·vue.js
Jinxiansen02112 小时前
Vue 3 实战:【加强版】公司通知推送(WebSocket + token 校验 + 心跳机制)
前端·javascript·vue.js·websocket·typescript
MrSkye2 小时前
React入门:组件化思想?数据驱动?
前端·react.js·面试