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

相关推荐
京东云开发者几秒前
行云前端重构之路:从单体应用到 Monorepo 的血泪史
前端
whale fall2 分钟前
npm install安装不成功(node:32388)怎么解决?
前端·npm·node.js
疯狂动物城在逃flash12 分钟前
数据库入门:SQL学习路线图与实战技巧
前端
前端小巷子17 分钟前
跨域问题解决方案:开发代理
前端·javascript·面试
前端_逍遥生18 分钟前
Chrome 插件开发到发布完整指南:从零开始打造 TTS 朗读助手
前端·chrome
Mintopia18 分钟前
Three.js 材质与灯光:一场像素级的光影华尔兹
前端·javascript·three.js
天涯学馆19 分钟前
JavaScript 跨域、事件循环、性能优化面试题解析教程
前端·javascript·面试
掘金一周28 分钟前
别再用 100vh 了!移动端视口高度的终极解决方案| 掘金一周7.3
前端·后端
晴殇i30 分钟前
CSS 迎来重大升级:Chrome 137 支持 if () 条件函数,样式逻辑从此更灵活
前端·css·面试
咚咚咚ddd32 分钟前
cursor mcp实践:网站落地页性能检测报告(browser-tools)
前端