React16源码: context用法与createContext源码实现

洞悉细节!react 16.8.6源码分析-4 children遍历 前言 作为一个前端页面仔和需求粉碎机,在日常的工作中重复雷同的业务需求,能够获得的提高是很有限的。要想跳出此山中,开阔新视野,笔者墙裂建议大家阅读市面上顶尖开源库的源码。这是学习和掌握js语言特性的绝佳机会(前端发展到现在,大型应用高度依赖框架,正常情况下普通开发者是没有机会接触底层的语言特性),同时也是深刻理解框架底层思维的契机。这里笔者选择react第一个开刀,市面上不少关于react源码分析的文章要么过于老旧,要么只截取部分代码或者是伪代码,笔者这里将选取react的16.8.6版本作为示例,从第0行 阅读详情

context


1 )概述

  • 在react的设计中,组件与组件之间的通信通常是
    • 父组件通过 props 给子组件传递子组件需要的属性
    • 父组件通过传递一些回调函数给子组件
    • 让子组件在某些特定的时候,可以调用一些父组件的特性
  • 这种情况,会存在一个问题
    • 就是react的应用中组件和组件之间并不一定只有父子关系
    • 还会存在着像父子嵌套多层之后,第一层和最下层的组件
    • 他们之间是一个主孙的一个关系
    • 他们中间会隔着好几层不同的组件,如果通过props进行一个传递,是不太现实的
    • 还有就是中间的那几层组件,不一定是我们自己写的
    • 中间的组件要去传递这个 props,其实是完全没有意义的事情
  • 所以,react 提供了一个 context 的一个使用方式
    • 在上级组件中,我们提供了一个 context 对象之后
    • 只要是在它下面渲染的组件都可以通过 context 这个属性去获取到它提供的这部分内容
    • 以此达到一个跨越多层组件传递信息的一个功能
  • context 有两种实现方式
    • 第一种,是通过老的 context 的 API 叫做 childContextTypes
      • 老的 childContextTypes 在react17 这个大版本发布的时候被废弃
      • 历史原因,用的还挺多
    • 第二种, 是通过新版提供的 createContext 这个API
      • 这块下面会分析下源码

用法示例

1 )示例演示

这个示例,演示了 新旧 两个 api 的用法

import React from 'react'
import PropTypes from 'prop-types'

const { Provider, Consumer } = React.createContext('default')

// 定义一个父组件 作为 最外层
class Parent extends React.Component {
  state = {
    childContext: '123',
    newContext: '456',
  }
  // react 静态方法 api
  getChildContext() {
    return { value: this.state.childContext, a: 'aaaaa' }
  }
  render() {
    return (
      <>
        <div>
          <label>childContext:</label>
          <input
            type="text"
            value={this.state.childContext}
            onChange={e => this.setState({ childContext: e.target.value })}
          />
        </div>
        <div>
          <label>newContext:</label>
          <input
            type="text"
            value={this.state.newContext}
            onChange={e => this.setState({ newContext: e.target.value })}
          />
        </div>
        {/* 基于 Provider来传递 */}
        <Provider value={this.state.newContext}>{this.props.children}</Provider>
      </>
    )
  }
}

// 定义第二个父组件 作为 中间层
class Parent2 extends React.Component {
  // { value: this.state.childContext, a: 'bbbbb' }
  getChildContext() {
    return { a: 'bbbbb' }
  }

  render() {
    return this.props.children
  }
}

// 定义第一个子组件 内部使用 Consumer
function Child1(props, context) {
  console.log(context)
  return <Consumer>{value => <p>newContext: {value}</p>}</Consumer>
}

// 声明子组件需要的 props
Child1.contextTypes = {
  value: PropTypes.string,
}

// 定义第二个子组件
class Child2 extends React.Component {
  render() {
    return (
      <p>
        childContext: {this.context.value} {this.context.a}
      </p>
    )
  }
}

// Child2.contextType = Consumer
// 声明 子组件2 需要的 props
Child2.contextTypes = {
  value: PropTypes.string,
  a: PropTypes.string,
}

// 父组件不声明,子组件无法获取 props
Parent.childContextTypes = {
  value: PropTypes.string,
  a: PropTypes.string,
}

// 父组件不声明,子组件无法获取 props
Parent2.childContextTypes = {
  a: PropTypes.string,
}

// 最终的组件树,不同组件的嵌套
export default () => (
  <Parent>
    <Parent2>
      <Child1 />
      <Child2 />
    </Parent2>
  </Parent>
)

2 )关于 childContextTypes 旧版API的说明

  • 主要以 Child2 组件来说明

  • 上级组件中声明这个 getChildContext 这个方法

  • 然后return的这个对象, 就是作为子组件当中能够获取这个 context 的对象

  • 但是有一点必须要注意,就是父组件必须要声明 childContextTypes, 即: Parent.childContextTypes

      Parent.childContextTypes = {
        value: PropTypes.string,
        a: PropTypes.string,
      }
    
  • 上层组件是必须要声明的

  • 如果不声明,它提供的这个 context ,子组件是无法获取到的

  • 想要获取上层组件提供的 context,需要在子组件 Child2 当中

  • 声明自己需要的 contextTypes, 例如

     Child2.contextTypes = {
       value: PropTypes.string,
       a: PropTypes.string,
     }
    
  • 它的内容也是跟上层组件的 childContextTypes 是一样

  • 两者区别是: 有或没有子组件

  • 在这个渲染的过程中,比如这个 Child2 组件,希望获取父层组件中提供的 context 里面的某几个属性

  • Chid 自己就需要去声明使用几个属性,为何这么做呢

    • 在react当中,它的上层组件不一定只有一个
    • 它上层组件中提供的 context 也不一定只有一个
    • 它们是会有一个 merge 的一个过程的
    • 所以在很多属性中 react 要知道你想要获取上层组件当中提供的context里面的哪几个属性
    • 所以要通过这种方式进行一个声明

3 )关于 createContext 新版API的分析

  • 通过 react.createContext,它返回了一个对象
    const { Provider, Consumer } = React.createContext('default')
    
  • 这个对象里面包含了 Provider 和 Consumer,是一个 context 提供方和 context 的订阅方
  • 这两个都是组件,我们通过在 Parent 这边我们提供了Provider, 然后上面指定 value
    <Provider value={this.state.newContext}>{this.props.children}</Provider>
    
    • 这个 value 就是 context,即提供的context的属性信息
    • 在它的子树下面,只需要在想要用到context的地方
    • 通过这个 Consumer 组件 (它传入的是一个回调方法)
    • 这个方法是一个function Component,它接收它的一个value
    • 并且把这个想要渲染的东西渲染出来就可以了
      // 声明子组件需要的 props
      Child1.contextTypes = {
       value: PropTypes.string,
      }
      
  • 所以Provider 和 Consumer 是一一对应的关系
  • 在上层组件里面定义之后,子组件里面你想要在哪个地方用到这个属性
  • 你再去专门用这个组件去进行一个渲染就可以了

4 )为什么要弃用老的API要改成这种新的API

  • 因老的API它对于context提供方,它下层的所有组件的影响太大了
  • 它会导致它下层的所有组件, 即便在没有任何更新的情况下,它每一次更新的过程当中
  • 仍然要进行完整的渲染,所以对性能的损耗会非常大

createContext 源码分析

定位到 reactContext.js

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import {REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';

import type {ReactContext} from 'shared/ReactTypes';

import warningWithoutStack from 'shared/warningWithoutStack';
import warning from 'shared/warning';

export function createContext<T>(
  defaultValue: T,
  calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
  if (calculateChangedBits === undefined) {
    calculateChangedBits = null;
  } else {
    if (__DEV__) {
      warningWithoutStack(
        calculateChangedBits === null ||
          typeof calculateChangedBits === 'function',
        'createContext: Expected the optional second argument to be a ' +
          'function. Instead received: %s',
        calculateChangedBits,
      );
    }
  }

  const context: ReactContext<T> = {
    $$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,
    // These are circular
    Provider: (null: any),
    Consumer: (null: any),
  };

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

  let hasWarnedAboutUsingNestedContextConsumers = false;
  let hasWarnedAboutUsingConsumerProvider = false;

  if (__DEV__) {
    // A separate object, but proxies back to the original context object for
    // backwards compatibility. It has a different $$typeof, so we can properly
    // warn for the incorrect usage of Context as a Consumer.
    const Consumer = {
      $$typeof: REACT_CONTEXT_TYPE,
      _context: context,
      _calculateChangedBits: context._calculateChangedBits,
    };
    // $FlowFixMe: Flow complains about not setting a value, which is intentional here
    Object.defineProperties(Consumer, {
      Provider: {
        get() {
          if (!hasWarnedAboutUsingConsumerProvider) {
            hasWarnedAboutUsingConsumerProvider = true;
            warning(
              false,
              '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;
        },
      },
      Consumer: {
        get() {
          if (!hasWarnedAboutUsingNestedContextConsumers) {
            hasWarnedAboutUsingNestedContextConsumers = true;
            warning(
              false,
              '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;
        },
      },
    });
    // $FlowFixMe: Flow complains about missing properties because it doesn't understand defineProperty
    context.Consumer = Consumer;
  } else {
    context.Consumer = context;
  }

  if (__DEV__) {
    context._currentRenderer = null;
    context._currentRenderer2 = null;
  }

  return context;
}
  • 定位到 createContext, 它接收的是一个 defaultValue 和 calculateChangeBits方法
    • calculateChangeBits方法是用来计算新老context它们的一个变化的
  • 在这里,它声明了一个context对象, 这个对象跟之前的 ReactElement 非常的像
    • 它有一个 $$typeof, 这里的 $$typeof 跟 ReactElement 里面的 $$typeof 是不一样的
  • 下面有这两个属性 _currentValue, _currentValue2
    • 这两个属性它们的用处是一样的,使用的地方会不一样,比如说,不同平台里面会不一样
    • 它的 _currentValue 是用来记录 Provider上面提供的这个 value
    • 在有变化的情况下,它就会更新到这个 _currentValue 上面,这就是用来记录最新的context的值的
  • 下面会有一个 Provider和 Consumer
  • 然后再接下去, 有一个 context.Provider 这个对象
     context.Provider = {
       $$typeof: REACT_PROVIDER_TYPE,
       _context: context,
     };
    
    • 这个_context指向这个context的对象
  • 忽略DEV相关的判断代码
  • 在最后我们看到 context.Consumer = context
    • 也就是说 Consumer是指向这个对象自己的
    • 在Consumer进行渲染的时候,它要去获取这个value
    • 直接从它本身上面去获取到这个 _currentValue
    • 就可以拿到最新的 context 的值了,然后再调用Consumer 的回调方法把它传进去
    • 就可以渲染出最新的内容
  • 所以这就是它的一个基本的实现原理。
  • 整个context 新的contextAPI的一个源码不是特别复杂,但是整个通信过程并不止这一点
  • 在这里,主要是弄清楚 Provider 和 Consumer 的关系
  • 需要注意的是
    • 返回的这个 Provider 和 Consumer,它们里面都有 $$typeof
    • 它们并不是用替代 ReactElement 里面的 $$typeof
    • 因为它返回的这个对象是整体, 是作为 ReactElement 里面的 type 属性去存储的
    • 所以跟这边的 $$typeof 是完全没有任何关系的
    • 也就是说 type 里面它还有一个 $$typeof, 表明它是一个context的 Provider,或 Consumer
    • 这个问题在前文也有提过
React Context API 原理深度解析:不是状态管理,而是渲染引擎级通信机制 Context API 是 React 16.3 引入的跨层级数据传递基础设施,其本质并非状态管理工具,而是基于 Fiber 架构构建的上下文绑定机制。它通过特殊的 fiber tag(ContextProvider)、依赖链表(contextDependencies)和位掩码(changedBits)实现细粒度更新调度,使 context 消费具备 O(1) 读取性能与可中断渲染能力。相比 Redux 等应用层状态库,Context 的技术价值在于零开销的环境配置分发(如 theme、locale、aut 阅读详情

相关推荐

开源机器学习平台tipdm

TipDM建模平台 tipdm开源的python机器学习建模平台,用户通过拖拉就可以实现模型开发。 项目结构 backend :为java后台服务代码 frontedn :为vue js前端代码 mock-oauth-server:为java认证服务代码 主要分析backend后台服务代码,典型的java spring mvc web项目,项目结构如下: framework-common:公共代码,主要为工具类,spring相关公用类 framework-model: 实体..

wflh323的专栏 4250

react 创建上下文(createContext)使用(超详细)

原本组件传递需要一层一层props传递,传递太过繁琐,例如以下代码: import React from 'react' function NeedData(props) { return <div>{props.value2}</div>; } // 中间组件 function Middle(props) { return ( <div> <NeedData value2={props.value} /&

前端-小鑫 2万+

从零到一:手把手教你用海康VisionMaster完成第一个字符识别项目(附完整流程与避坑点)

本文详细介绍了如何使用海康VisionMaster完成首个字符识别项目,涵盖从环境搭建、图像预处理到系统集成的完整流程。特别分享了Blob分析配置、字符识别优化等实战技巧,帮助开发者避开常见陷阱,快速掌握VisionMaster的基本操作,提升工业自动化场景下的字符识别效率。

weixin_29313423的博客 672

原来react的createContext 这么简单

今天看了下react中createContext相关的源码,特意在这里拿出来分享下。同时也会体现出关于本人看源码的技巧 本文采用源码分析以及源码断点调试的方式进行列举 用法 import React from "react"; const CounterContext = React.createContext() // 子类组件 function Counter() { const {count, setCount} = React.useContext(CounterContext) ..

weixin_42373116的博客 6219

【python】epub的电子书繁体字与简体字转换

中文繁体与简体转换

xiexianyou666的博客 3092

ArcGIS:根据DEM数据提取区域水系

利用DEM数据提取区域水系是常见的水文分析应用,为了获取区域的矢量水系数据,我们可以通过ArcGIS对高程DEM数据进行水文分析,为地表水流建立模型,进而获取到该地的水系数据,DEM数据精度越高,获取到的水系数据精度也就越高。以下介绍如何用ArcGIS提供的工具提取水系。

Oruizn的博客 9111

React createContext 与 useContext 的基本使用

对象的组件,这个组件会从组件树中离自身最近的那个匹配的。只有当组件所处的树中没有匹配到。

卡尔特斯 2701

AIAgent知识表示与推理:基于本体论与规则引擎的实现

AIAgent知识表示与推理:基于本体论与规则引擎的实现 1. 背景介绍 人工智能(AI)系统的核心是知识表示和推理,这是实现智能行为的基础。在过去的几十年里,人工智能领域一直致力于研究如何有效地表示和推理知识,以支持智能系统的决策和行为。其中,基于本体论的知识表示和

AI天才研究院 1498

React 开发 - React.createContext 上下文

是用于创建上下文,方便组件树中共享数据,而不需要通过props逐层传递数据。

2401_87546826的博客 1260

Android13 CarPowerManagementService onApPowerStateChange流程分析

如下为CarPower上电的流程图:下面我们从代码角度分析:在CPMS的init方法中会调用onApPowerStateChange(CpmsState.WAIT_FOR_VHAL, CarPowerManager.STATE_WAIT_FOR_VHAL); 设置ApPowerState为STATE_WAIT_FOR_VHAL: 在onApPowerStateChange中调用PowerHandler 的handlePowerStateChange方法: 在handlePowerStateChang

liuning1985622的博客 926

【目标检测】IoU(交并比)详解及代码实现

1. IOU概述        IoU的全称为交并比(Intersection over Union),是目标检测中使用的一个概念,IoU计算的是“预测的边框”和“真实的边框”的交叠率-,即它们的交集和并集的比值。最理想情况是完全重叠,即比值为1。 2. IoU计算        IoU等于“预测的边框”和“真实的边框”之间交集和并集的比值。IoU计算如下图,B1为真实

Roaddd的博客 2万+

React 中 createContext 和 useContext 的深度应用与优化实战

本文深入探讨了 React 中createContext和useContext的应用。从基础概念入手,详细介绍了它们在组件树中共享数据的原理和优势。通过一个完整的代码示例,展示了如何创建ContentProvider组件来封装上下文,以及在多个嵌套层级的函数组件中使用useEventContext钩子获取和操作共享数据。文中不仅给出了详细的代码实现步骤,还对目录结构进行了清晰展示,并配有效果截图,帮助读者直观地理解上下文在 React 应用中的强大功能,同时还对代码进行了优化,让读者能够学习到更高效、更规

cx18333的博客 2164

电路板保险丝如何选型

在电路板保险丝选型过程中,需要考虑多个因素以确保其适合特定的电路需求。最后,经过以上程序选定的样品需要在实际的电路中进行测试,以验证所选择的保险丝是否合适。

weixin_57010826的博客 1310

react之context学习笔记(contextTypes/childContextTypes,createContext,useContext)使用方法,及部分源码翻译

在react16.0之前,如果想要使用context,需要使用childContextTypes以及contextTypes // 父组件 class TestContext extends Component { state = { childContext: "123" } constructor(props) { super(props...

qq_28992047的博客 2926

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

React旧项目升级必备:create-react-context让你提前用上Context API

还在为React旧项目无法使用Context API而烦恼吗?create-react-context作为React官方Context API提案的兼容填充库(Polyfill),让你在React 16之前的项目中也能享受到上下文传递的便利。本文将带你快速掌握这个实用工具的安装使用与最佳实践,轻松解决组件间数据传递难题。 ## 📦 一分钟快速安装 create-react-context的安

gitblog_00303的博客 633

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

react源码解析17.context

react源码解析17.context 视频讲解(高效学习):进入学习 往期文章: 1.开篇介绍和面试题 2.react的设计理念 3.react源码架构 4.源码目录结构和调试 5.jsx&核心api 6.legacy和concurrent模式入口函数 7.Fiber架构 8.render阶段 9.diff算法 10.commit阶段 11.生命周期 12.状态更新流程 13.hooks源码 14.手写hooks 15.scheduler&Lane 16.concurrent模式 17.c

zz2000aa的博客 258

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

react源码解析第17节.context源码

react源码解析第17节.context源码 视频讲解(高效学习):进入学习 往期文章: 1.开篇介绍和面试题 2.react的设计理念 3.react源码架构 4.源码目录结构和调试 5.jsx&核心api 6.legacy和concurrent模式入口函数 7.Fiber架构 8.render阶段 9.diff算法 10.commit阶段 11.生命周期 12.状态更新流程 13.hooks源码 14.手写hooks 15.scheduler&Lane 16.concurrent模式

zz2000aa的博客 286

React16源码: React中NewContext的源码实现

新的 context API 是一个组件化的使用方式, context的提供方和订阅方都是独立的, 没有什么附带的性能影响

Wang的专栏 665

从编程中的context来说react - context(16.x)

从编程中的context来说react - context(16.x)编程上的contextwhat is context ?从语文的角度理解从编程的角度理解react - context何时使用context使用 context, 我们可以避免通过中间元素传递 propsAPI注意事项拓展 参考资料: 编程中什么是「Context(上下文)」?. react - context官方文档. Re...

W_l_l的博客 553

React上下文(Context)完全指南:React Bits-CN中的状态共享技巧

React上下文(Context)是React组件树中共享数据的强大机制,特别适合解决跨组件状态传递的难题。本文将详细介绍React Bits-CN项目中的上下文使用模式,帮助你掌握高效的状态共享技巧。 ## 为什么需要React上下文? 在传统的React应用中,数据通过props在组件间传递。当组件层级较深或需要跨多个组件共享数据时,这种"props钻取"方式会变得繁琐且难以维护。Reac

gitblog_00145的博客 484

React面试核心:Fiber架构与Hooks原理深度解析

React作为现代前端开发的核心框架,其Fiber架构和Hooks机制是理解框架设计的关键。Fiber架构通过任务分片和优先级调度实现了高性能渲染,而Hooks的链表存储机制则革新了状态管理方式。这些底层原理不仅决定了React应用的性能表现,也是面试中区分工程师水平的重要标尺。在实际开发中,合理应用React.memo、useMemo等优化手段能有效解决组件重渲染问题,而useEffect的清理机制则是避免内存泄漏的核心。从虚拟DOM diff算法到Server Components等前沿特性,掌握Rea

chuannaoxuan4674的博客 380

React Hook - useContext

useContext是React提供的一个Hook,用于简化从Context中获取共享数据的代码。它替代了传统的<XxxContext.Consumer>标签,使得在函数组件中获取共享数据更加简洁。通过useContext,开发者可以直接获取由React.createContext()创建的共享数据对象的值,而无需逐层传递属性。useContext的基本用法是传入一个Context对象,并返回该Context的当前值。它适用于需要从多个组件中访问相同数据的场景,如主题、用户信息等。使用useCo

m0_52222401的博客 1347
上一篇: React16源码: createRef与forwardRef源码实现
下一篇: React16源码: ConcurrentMode的使用及源码实现
Wang's Blog
博客等级 码龄12年 7438粉丝 2421原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值