React Context源码是怎么实现的呢

news2024/11/27 0:28:36

目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api。大多数项目不会直接使用 createContext 然后向下面传递数据,而是采用第三方库(react-redux)。

想想项目中是不是经常会用到 @connect(...)(Comp) 以及 <Provider value={store}><App /></Provider>

什么是 Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

一个顶层数据,想要传递到某些深层组件,通过 props 逐层传递将会非常繁琐,使用 Context 可避免显式地通过组件树逐层传递 props

Context 使用示例

import React, { Component, createContext, useConText } from 'react'
const ColorContext = createContext(null)
const { Provider, Consumer } = ColorContext

console.log('ColorContext', ColorContext)
console.log('Provider', Provider)
console.log('Consumer', Consumer)

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      color: 'red',
      background: 'cyan',
    }
  }
  render() {
    return <Provider value={this.state}>{this.props.children}</Provider>
  }
}
function Article({ children }) {
  return (
    <App>
      <h1>Context</h1>
      <p>hello world</p>
      {children}    </App>
  )
}
function Paragraph({ color, background }) {
  return (
    <div style={{ backgroundColor: background }}>
      <span style={{ color }}>text</span>
    </div>
  )
}
function TestContext() {
  return (
    <Article>
      <Consumer>{state => <Paragraph {...state} />}</Consumer>
    </Article>
  )
}

export default TestContext

页面呈现出的效果

打印 ColorContextProviderConsumer

createContext

// createContext 可以让我们实现状态管理
// 还能够解决传递 Props drilling 的问题
// 假如一个子组件需要父组件的一个属性,但是中间间隔了好几层,这就会出现开发和维护的一个成本。这时候就可以通过这个 API 来解决
function createContext(defaultValue, calculateChangedBits) {
  var context = {
    ?typeof: REACT_CONTEXT_TYPE,
    _calculateChangedBits: calculateChangedBits,
    // As a workaround to support multiple concurrent renderers, we categorize
    // some renderers as primary and others as secondary. We only expect
    // there to be two concurrent renderers at most: React Native (primary) and
    // Fabric (secondary); React DOM (primary) and React ART (secondary).
    // Secondary renderers store their context values on separate fields.
    // 以下两个属性是为了适配多平台
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    // Used to track how many concurrent renderers this context currently
    // supports within in a single renderer. Such as parallel server rendering.
    _threadCount: 0,
    // These are circular
    Provider: null,
    Consumer: null
  };

  // 以下的代码很简单,就是在 context 上挂载 Provider 和 Consumer,让外部去使用
  context.Provider = {
    ?typeof: REACT_PROVIDER_TYPE,
    _context: context
  };

  var Consumer = {
    ?typeof: REACT_CONTEXT_TYPE,
    _context: context,
    _calculateChangedBits: context._calculateChangedBits
  };

  context.Consumer = Consumer;
  context._currentRenderer = null;
  context._currentRenderer2 = null;
  return context;
}

react 包里面仅仅是生成了几个对象,比较简单,接下来看看它发挥作用的地方。

Consumer children 的匿名函数里面打 debugger。

相关参考视频讲解:进入学习

查看调用栈

主要是 newChildren = render(newValue);newChildrenConsumerchildren 被调用之后的返回值,render 就是 childrennewValue 是从 Provider value 属性的赋值。

newProps

newValue

接下来看 readContext 的实现

let lastContextDependency: ContextDependency<mixed> | null = null;
let currentlyRenderingFiber: Fiber | null = null;
// 在 prepareToReadContext 函数
currentlyRenderingFiber = workInProgress;


export function readContext<T>(
  context: ReactContext<T>,
  observedBits: void | number | boolean,
): T {
    let contextItem = {
      context: ((context: any): ReactContext<mixed>),
      observedBits: resolvedObservedBits,
      next: null,
    };

    if (lastContextDependency === null) {
      // This is the first dependency for this component. Create a new list.
      lastContextDependency = contextItem;
      currentlyRenderingFiber.contextDependencies = {
        first: contextItem,
        expirationTime: NoWork,
      };
    } else {
      // Append a new context item.
      lastContextDependency = lastContextDependency.next = contextItem;
    }
  }

  // isPrimaryRenderer 为 true,定义的就是 true
  // 实际就是一直会返回  context._currentValue
  return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}

跳过中间,最后一句 return context._currentValue,而

就把顶层传下来的 context 的值取到了

context 为什么从上层可以一直往下面传这点现在还没有看懂,后面熟悉跨组件传递的实现之后再写一篇文章解释,囧。

Context 的设计非常特别

Provider Consumer 是 context 的两个属性。

  var context = {
    ?typeof: REACT_CONTEXT_TYPE,
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    Provider: null,
    Consumer: null
  };

Provider?typeofREACT_PROVIDER_TYPE,它带有一个 _context 属性,指向的就是 context 本身,也就是自己的儿子有一个属性指向自己!!!

  context.Provider = {
    ?typeof: REACT_PROVIDER_TYPE,
    _context: context
  };

Consumer?typeofREACT_CONTEXT_TYPE,它带也有一个 _context 属性,也是自己的儿子有一个属性指向自己!!!

  var Consumer = {
    ?typeof: REACT_CONTEXT_TYPE,
    _context: context,
    _calculateChangedBits: context._calculateChangedBits
  };

所以可以做一个猜想, Provider 的 value 属性赋予的新值肯定通过 _context 属性传到了 context 上,修改了 _currentValue。同样,Consumer 也是依据 _context 拿到了 context_currentValue,然后 render(newValue) 执行 children 函数。

useContext

useContext 是 react hooks 提供的一个功能,可以简化 context 值得获取。

下面看使用代码

import React, { useContext, createContext } from 'react'
const NameCtx = createContext({ name: 'yuny' })
function Title() {
  const { name } = useContext(NameCtx)
  return <h1># {name}</h1>
}
function App() {
  return (
    <NameCtx.Provider value={{ name: 'lxfriday' }}>
      <Title />
    </NameCtx.Provider>
  )
}
export default App

我么初始值给的是 {name: 'yuny'},实际又重新赋值 {name: 'lxfriday'},最终页面显示的是 lxfriday

useContext 相关源码

先看看 react 包中导出的 useContext

/** * useContext * @param Context {ReactContext} createContext 返回的结果 * @param unstable_observedBits {number | boolean | void} 计算新老 context 变化相关的,useContext() second argument is reserved for future  * @returns {*} 返回的是 context 的值 */
export function useContext<T>(
  Context: ReactContext<T>,  unstable_observedBits: number | boolean | void,
) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useContext(Context, unstable_observedBits);
}
// Invalid hook call. Hooks can only be called inside of the body of a function component. 
function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return dispatcher;
}
/** * Keeps track of the current dispatcher. */
const ReactCurrentDispatcher = {
  /**   * @internal
   * @type {ReactComponent}   */
  current: (null: null | Dispatcher),
};

看看 Dispatcher,都是和 React Hooks 相关的。

再到 react-reconciler/src/ReactFiberHooks.js 中,有 HooksDispatcherOnMountInDEVHooksDispatcherOnMount,带 InDEV 的应该是在 development 环境会使用到的,不带的是在 `production 会使用到。

const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
};

HooksDispatcherOnMountInDEV = {
   // ... 
   useContext<T>(
      context: ReactContext<T>,
      observedBits: void | number | boolean,
    ): T {
      return readContext(context, observedBits);
    },
}

在上面 useContext 经过 readContext 返回了 context 的值,readContext 在上面有源码介绍。

debugger 查看调用栈

初始的 useContext

HooksDispatcherOnMountInDEV

readContext

经过上面源码的详细分析, 大家对 context 的创建和 context 取值应该了解了,context 设计真的非常妙!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/42431.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

非互联网客户收入近6成,阿里云进入新周期

近日&#xff0c;阿里巴巴发布了截至2022年9月30日止季度&#xff08;2023财年第二季度&#xff0c;阿里财年从每年4月1日至第二年3月31日&#xff09;业绩。 在除去阿里内部使用的额度&#xff0c;抵销跨分部交易后&#xff0c;阿里云业务分部&#xff08;包括阿里云和钉钉&am…

【web前端期末大作业】基于html+css+javascript+jquery技术设计的音乐网站(44页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

【华为上机真题 2022】字符串加密

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

【Linux】基本指令(三)

文章目录sort 指令find 指令which 指令alias 指令grep 指令top 指令zip/unzip指令tar 指令bc 指令unname 指令history 指令shutdown 指令热键补充sort 指令 &#x1f495; Linux sort 命令用于将文本文件内容加以排序并且可针对文本文件的内容&#xff0c;以行为单位来排序。 …

03 探究Kubernetes工作机制的奥秘

自动化的运维管理&#xff1a;探究Kubernetes工作机制的奥秘 文章目录1. 云计算时代的操作系统2. kubernets 的基本架构3.节点内部的结构3.1 master 节点3.2 node 节点4. Kubernetes 的大致工作流程5.插件有哪些&#xff1f;5.1 重要的插件6. kubernetes 架构思维导图7. 思考的…

制作一个简单HTML校园网页(HTML+CSS)学校网站制作 校园网站设计与实现

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

RabbitMQ工作队列

工作队列(又称任务队列)的主要思想是避免立即执行资源密集型任务&#xff0c;而不得不等待它完成。 相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时&#xff0c;这些工作线程将一起处…

电商购物平台的不断完善

目录 1.java编译环境的创建&#xff0c;与所需要用到的插件 第一个电商平台 1.初步思路&#xff1a; 2.确定java类 3.源码分析 成果&#xff1a; 第二个电商购物平台 代码&#xff1a; 最终的成果&#xff1a; 1.java编译环境的创建&#xff0c;与所需要用到的插件 (1…

链夹式烟苗注水移栽机的总体设计

目 录 1 引言 1 1.1课题来源及研究的目的和意义 1 1.2农艺要求 1 1.3链夹式烟苗注水移栽机的发展现状 1 1.4研究内容 3 2 链夹式烟苗注水移栽机的设计方案 4 2.1链夹式烟苗注水移栽机总体方案设计思路 4 2.2链夹式烟苗注水移栽机工作原理 5 3 链夹式烟苗注水移栽机具体设计 6 3…

带你入门HTML+CSS网页设计,编写网页代码的思路

带你入门HTMLCSS网页设计&#xff0c;编写网页代码的思路 这篇文章主要给大家详细解释一下这些代码的作用和意义&#xff0c;以及编写网页代码的格式与思路。 下面我贴上html代码&#xff1a; <!--HTML--> <div> <h2>这是我的第一个网页</h2> <p&…

Metabase学习教程:仪表盘-3

自定义联动&#xff1a;选择当人们单击仪表盘中的图表时会发生什么 您可以设置仪表盘部件以将用户导航到仪表盘、保存的问题和URL&#xff0c;并使用仪表盘中的值更新目标仪表盘的筛选器&#xff0c;或参数化指向外部站点的链接。 Metabase提供了一些简单的构建块&#xff0c…

谷粒学院——Day08【课程发布-课程大纲和课程发布】

富文本编辑器Tinymce 一、Tinymce可视化编辑器 参考 https://panjiachen.gitee.io/vue-element-admin/#/components/tinymce https://panjiachen.gitee.io/vue-element-admin/#/example/create 二、组件初始化 Tinymce是一个传统javascript插件&#xff0c;默认不能用于V…

MySQL安装部署

1、卸载mariadb 查看是否有默认的mariadbrpm -qa|grep mariadb 如果有&#xff0c;卸载rpm -e --nodeps mariadb-libs-5.5.56-2.el7.x86_64&#xff0c;然后删除etc目录下的my.cnfrm -rf /etc/my.cnf&#xff08;注意这里需要确定tar包里是否有默认的cnf文件&#xff0c;在su…

NLP实践!文本语法纠错模型实战,搭建你的贴身语法修改小助手 ⛵

&#x1f4a1; 作者&#xff1a;韩信子ShowMeAI &#x1f4d8; 深度学习实战系列&#xff1a;https://www.showmeai.tech/tutorials/42 &#x1f4d8; 自然语言处理实战系列&#xff1a;https://www.showmeai.tech/tutorials/45 &#x1f4d8; 本文地址&#xff1a;https://sho…

AI智能机器人的测评以及部署

作为业内人士&#xff0c;今天给大家测评下电销机器人。究竟是什么样的电销机器人才是让客户满意的&#xff1f; 咱们先来说说电销机器人到底是什么&#xff1f; 相信很多人都对电销机器人没有过多的了解。甚至还有人会问&#xff1b;“什么&#xff1f;机器人&#xff1f;啥样…

两万字详细解读AQS,你真的了解它吗?

1、JUC的由来 synchronized 关键字是JDK官方人员用C代码写的&#xff0c;在JDK6以前是重量级锁。Java大牛 Doug Lea对 synchronized 在并发编程条件下的性能表现不满意就自己写了个JUC&#xff0c;以此来提升并发性能&#xff0c;本文要讲的就是JUC并发包下的AbstractQueuedSy…

Listen、Attention、Spell模型

LAS是一个做语音识别的经典seq2seq模型&#xff0c;主要分为三个部分Listen、Attention、Spell Listen Listen部分就是一个encoder。 输入声学特征向量&#xff0c;提取信息、消除噪声&#xff0c;输出向量。 encoder可以是RNN 也可以是CNN。比较常见的是先用CNN&#xff0…

第三章变量

第三章变量 查看javaAPI文档的网址&#xff1a; https://www.matools.com/ 3.1程序中号的使用 1&#xff09;当左右两边都是数值型时&#xff0c;则做加法运算 2&#xff09;当左右两边有一方为字符串&#xff0c;则做拼接运算 3&#xff09;运算顺序从左到右 System,out.prin…

Springboot+vue校园新闻网站idea

将系统需求进行分析总结&#xff0c;系统需求如下&#xff1a; 系统可以运行在Windows操作系统平台上&#xff0c;并通过友好的界面进行管理 系统用户分为游客&#xff0c;登录用户&#xff0c;管理员 游客可以浏览新闻 游客可以浏览评论 管理员具有用户所有的权限 管理员还可以…

别再把Tableau、PowerBI吹上天了,在中国根本用不起来,看看为啥

工作业务相关&#xff0c;这几年接触BI较多&#xff0c;借此浅聊下我对BI工具以及市场的看法&#xff0c;原创禁止转载。 1、BI并不玄乎&#xff0c;本质就是实现简单数据分析和可视化的工具 很多人觉得BI玄乎&#xff0c;其实很大程度是因为BI厂家给造的名词太多了&#xff…