当前位置:首页 >娱乐 >React Context的核心实现,就五行代码 React Context的核代码核心实现

React Context的核心实现,就五行代码 React Context的核代码核心实现

2024-06-30 16:44:26 [百科] 来源:避面尹邢网

React Context的核代码核心实现,就五行代码

作者:卡颂 开发 前端 React Context的心实现行实现就是个典型例子,当剔除无关功能的核代码干扰后,他的心实现行核心实现,仅需「5行代码」。核代码本文就让我们看看React Context的心实现行核心实现。

大家好,核代码我卡颂。心实现行

React Context的核心实现,就五行代码 React Context的核代码核心实现

很多项目的核代码源码非常复杂,让人望而却步。心实现行但在打退堂鼓前,核代码我们应该思考一个问题:源码为什么复杂?

React Context的核心实现,就五行代码 React Context的核代码核心实现

造成源码复杂的心实现行原因不外乎有三个:

React Context的核心实现,就五行代码 React Context的核代码核心实现

  1. 功能本身复杂,造成代码复杂。核代码
  2. 编写者功力不行,心实现行写的核代码代码复杂。
  3. 功能本身不复杂,但同一个模块耦合了太多功能,看起来复杂。

如果是原因3,那实际理解起来其实并不难。我们需要的只是有人能帮我们剔除无关功能的干扰。

React Context的实现就是个典型例子,当剔除无关功能的干扰后,他的核心实现,仅需「5行代码」。

本文就让我们看看React Context的核心实现。

简化模型

Context的完整工作流程包括3步:

  1. 定义context
  2. 赋值context
  3. 消费context

以下面的代码举例:

const ctx = createContext(null);function App() {  return (  <ctx.Provider value={ 1}>   <Cpn />  </ctx.Provider> );}function Cpn() {  const num = useContext(ctx); return <div>{ num}</div>;}

其中:

  • const ctx = createContext(null) 用于定义。
  • <ctx.Provider value={ 1}> 用于赋值。
  • const num = useContext(ctx) 用于消费。

Context数据结构(即createContext方法的返回值)也很简单:

function createContext(defaultValue) {   const context = {     $$typeof: REACT_CONTEXT_TYPE,    Provider: null,    _currentValue: defaultValue  };  context.Provider = {     $$typeof: REACT_PROVIDER_TYPE,    _context: context  };  return context;}

其中context._currentValue保存context当前值。

context工作流程的三个步骤其实可以概括为:

  1. 实例化context,并将默认值defaultValue赋值给context._currentValue。
  2. 每遇到一个同类型context.Provier,将value赋值给context._currentValue。
  3. useContext(context)就是简单的取context._currentValue的值就行。

了解了工作流程后我们会发现,Context的核心实现其实就是步骤2。

核心实现

核心实现需要考虑什么呢?还是以上面的示例为例,当前只有一层<ctx.Provider>包裹<Cpn />:

function App() {  return (  <ctx.Provider value={ 1}>   <Cpn />  </ctx.Provider> );}

在实际项目中,消费ctx的组件(示例中的<Cpn/>)可能被多级<ctx.Provider>包裹,比如:

const ctx = createContext(0);function App() {  return (    <ctx.Provider value={ 1}>      <ctx.Provider value={ 2}>        <ctx.Provider value={ 3}>          <Cpn />        </ctx.Provider>        <Cpn />      </ctx.Provider>      <Cpn />    </ctx.Provider>  );}

在上面代码中,ctx的值会从0(默认值)逐级变为3,再从3逐级变为0,所以沿途消费ctx的<Cpn />组件取得的值分别为:3、2、1。

整个流程就像「操作一个栈」,1、2、3分别入栈,3、2、1分别出栈,过程中栈顶的值就是context当前的值。

基于此,context的核心逻辑包括两个函数:

function pushProvider(context, newValue) {  // ...}function popProvider(context) {  // ...}

其中:

  • 进入ctx.Provider时,执行pushProvider方法,类比入栈操作。
  • 离开ctx.Provider时,执行popProvider方法,类比出栈操作。

每次执行pushProvider时将context._currentValue更新为当前值:

function pushProvider(context, newValue) {  context._currentValue = newValue;}

同理,popProvider执行时将context._currentValue更新为上一个context._currentValue:

function popProvider(context) {  context._currentValue = /* 上一个context value */}

该如何表示上一个值呢?我们可以增加一个全局变量prevContextValue,用于保存「上一个同类型的context._currentValue」:

let prevContextValue = null;function pushProvider(context, newValue) {  // 保存上一个同类型context value  prevContextValue = context._currentValue;  context._currentValue = newValue;}function popProvider(context) {   context._currentValue = prevContextValue;}

在pushProvider中,执行如下语句前:

context._currentValue = newValue;

context._currentValue中保存的就是「上一个同类型的context._currentValue」,将其赋值给prevContextValue。

以下面代码举例:

const ctx = createContext(0);function App() {  return (  <ctx.Provider value={ 1}>   <Cpn />  </ctx.Provider> );}

进入ctx.Provider时:

  • prevContextValue赋值为0(context实例化时传递的默认值)。
  • context._currentValue赋值为1(当前值)。

当<Cpn />消费ctx时,取得的值就是1。

离开ctx.Provider时:

  • context._currentValue赋值为0(prevContextValue对应值)。

但是,我们当前的实现只能应对一层ctx.Provider,如果是多层ctx.Provider嵌套,我们不知道沿途ctx.Provider对应的prevContextValue。

所以,我们可以增加一个栈,用于保存沿途所有ctx.Provider对应的prevContextValue:

const prevContextValueStack = [];let prevContextValue = null;function pushProvider(context, newValue) {  prevContextValueStack.push(prevContextValue);   prevContextValue = context._currentValue; context._currentValue = newValue;}function popProvider(context) {  context._currentValue = prevContextValue; prevContextValue = prevContextValueStack.pop();}

其中:

  • 执行pushProvider时,让prevContextValue入栈。
  • 执行popProvider时,让prevContextValue出栈。

至此,完成了React Context的核心逻辑,其中pushProvider三行代码,popProvider两行代码。

两个有意思的点

关于Context的实现,有两个有意思的点。

第一个点:这个实现太过简洁(核心就5行代码),以至于让人严重怀疑是不是有bug?

比如,全局变量prevContextValue用于保存「上一个同类型的context._currentValue」,如果我们把不同context嵌套使用时会不会有问题?

在下面代码中,ctxA与ctxB嵌套出现:

const ctxA = createContext('default A');const ctxB = createContext('default B');function App() {   return (    <ctxA.Provider value={ 'A0'}>      <ctxB.Provider value={ 'B0'}>        <ctxA.Provider value={ 'A1'}>          <Cpn />        </ctxA.Provider>      </ctxB.Provider>      <Cpn />    </ctxA.Provider>  );}

当离开最内层ctxA.Provider时,ctxA._currentValue应该从'A1'变为'A0'。考虑到prevContextValue变量的唯一性以及栈的特性,ctxA._currentValue会不会错误的变为'B0'?

答案是:不会。

JSX结构的确定意味着以下两点是确定的:

  1. ctx.Provider的进入与离开顺序。
  2. 多个ctx.Provider之间嵌套的顺序。

第一点保证了当进入与离开同一个ctx.Provider时,prevContextValue的值始终与该ctx相关。

第二点保证了不同ctx.Provider的prevContextValue被以正确的顺序入栈、出栈。

第二个有意思的点:我们知道,Hook的使用有个限制 —— 不能在条件语句中使用hook。

究其原因,对于同一个函数组件,Hook的数据保存在一条链表上,所以必须保证遍历链表时,链表数据与Hook一一对应。

但我们发现,useContext获取的其实并不是链表数据,而是ctx._currentValue,这意味着useContext其实是不受这个限制影响的。

总结

以上五行代码便是React Context的核心实现。在实际的React源码中,Context相关代码远不止五行,这是因为他与其他特性耦合在一块,比如:

  • 性能优化相关代码
  • SSR相关代码

所以,当我们面对复杂代码时,不要轻言放弃。仔细分析下,没准儿核心代码只有几行呢?

责任编辑:姜华 来源: 魔术师卡颂 ReactContext

(责任编辑:知识)

    推荐文章
    • 安逸花还完钱了每个月还扣98 具体原因是怎样的?

      安逸花还完钱了每个月还扣98 具体原因是怎样的?安逸花是由马上消费金融推出的纯信用贷款,额度高,期限长,有不少人都在上面借过钱。其中有些人借钱后碰到一种奇怪的现象,明明把安逸花的欠款还上了竟然还在扣钱,比如有人安逸花还完钱了每个月还扣98,那么这是 ...[详细]
    • 星球大战外传剧集《安多》第二季将于明年8月播出

      星球大战外传剧集《安多》第二季将于明年8月播出根据主创 Tony Gilroy 的说法,《星球大战外传:侠盗一号》衍生的剧集《安多》第二季预计将于 2024 年 8 月再迪士尼+ 上播出。日前举办的星球大战庆典欧洲粉丝大会上,Gilroy宣布《安 ...[详细]
    • 有史以来最快闪充技术官宣:150W 来自realme

      有史以来最快闪充技术官宣:150W 来自realme传闻中欧加系的新百瓦快充在今天下午正式被realme首次官宣。realme手机官方微博发文称:“2020年2月24日真我X50 Pro 首次搭载65W闪充横空出世2020年7月16日realme 12 ...[详细]
    • 红牛之父的中国心

      红牛之父的中国心今年,是红牛品牌进入中国市场的第三十年。三十年前,将这一品牌带入中国的,正是红牛创始公司天丝集团的创始人许书标。你可以用很多标签去定义许书标:他被誉为红牛之父,是泰国最负盛誉企业家;他常年献身于公益事 ...[详细]
    • 借呗怎么变成信用贷了 借呗变成信用贷还能借款吗?

      借呗怎么变成信用贷了 借呗变成信用贷还能借款吗?借呗是大家耳熟能详的消费信用贷款,并且有很多人都在上面借过钱。可是有不少人发现自己的借呗变成了信用贷,就不是很清楚还能不能借款。那么借呗变成信用贷还能借款吗?这里就给大家来简单介绍下。1、借呗怎么变成 ...[详细]
    • 天玑9000:对不起 这次价格真低不了

      天玑9000:对不起 这次价格真低不了上周的时候很多厂商都明示或者暗示出了将会推出搭载联发科天玑9000处理器的新机,关于这款芯片,大家的期待就是它在保持不弱于骁龙8 Gen1的性能和带来更好功耗的前提下,终端价格还会比骁龙8 Gen1低 ...[详细]
    • 全球首发骁龙8的moto X30要上春晚了

      全球首发骁龙8的moto X30要上春晚了联想旗下的moto edge X30手机自从抢下了全球首发全新一代骁龙8处理器之后,热度就一直不减,今天早上,联想中国区手机业务部总经理陈劲又为该机带来了一条新消息预热。陈劲发微博称:“前天问了大家有 ...[详细]
    • 又一款骁龙8 Gen1旗舰官宣:25日发布 定制35mm大师镜头

      又一款骁龙8 Gen1旗舰官宣:25日发布 定制35mm大师镜头2月16日消息,今天早上,努比亚手机官方微博带来新机预告,新旗舰将于本月25日14:00发布。“致敬经典,重新定义#努比亚Z40Pro#人文影像新旗舰影像、性能双一流,新品即将发布!发布会时间:202 ...[详细]
    • 鲁西化工(000830.SZ)公布消息:拟开展外汇衍生品交易业务

      鲁西化工(000830.SZ)公布消息:拟开展外汇衍生品交易业务鲁西化工(000830.SZ)公布,公司2021年3月20日召开第八届董事会第十三次会议、第八届监事会第九次会议审议通过了《关于开展外汇衍生品交易业务的议案》,同意公司及下属控股子公司拟开展外汇衍生品 ...[详细]
    • Find X5 Pro天玑版为什么卖那么贵?这是官方答案

      Find X5 Pro天玑版为什么卖那么贵?这是官方答案昨日晚间,OPPO正式发布了旗下旗舰新机Find X5系列,其中自研芯片马里亚纳X和首款OPPO平板等产品都引起了业内很多用户的关注,关于OPPO这几款产品都有什么特性,OPPO后期的产品规划会如何? ...[详细]
    热点阅读