React Context

Context

https://juejin.cn/post/7244838033454727227?searchId=202404012120436CD549D66BBD6C542177

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

React 中数据通过 props 属性自上而下(由父及子)进行传递,但此种用法对于某些类型的属性而言极其繁琐(UI 主题,地区偏好),这些属性时应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显示的通过组件树逐层传递 props

  1. 创建 context

  2. 使用 context 的 Provider 进行包裹

  3. 使用 contextType 进行接收,this.context 读取

js 复制代码
const ThemeContext = React.createContext('light');
   class App extends React.Component {
    render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </hemeContext.Provider>
    )
    }
   }

function Toolbar() {
  return (
    <div><ThemeButton /></div>
  )
}

class ThemeButton extends React.Component {
// static contextType = ThemeContext
  return <Button theme={this.context} />
}
ThemedButton.contectType = ThemeContext
js 复制代码
function ThemedButton() {
  return (
    <ThemeContext.Consumer>
      {(value) => <button>{value}</button>}
    </ThemeContext.Consumer>
  );
}

context 用于不同层级的组件需要访问同样的数据。使得组件的复用性变差

如果只是想避免层层传递的一些属性,可以使用component composition

  • 将组件本身作为 props 传递下去
  • 这种对组件的控制减少了应用中需要传递的 props 数量,在很多场景下会使得代码更加干净,但是并不适用于每一个场景,这种将逻辑提升到组件树的更高层次会使得高层组件变得更加复杂,并且会强行将底层组件适应这样的样式

API

React.createContext

js 复制代码
const MyContext = React.createContext(defaultValue);

创建一个 Context 对象,当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值

只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。此默认值有助于在不使用 Provider 包装组件的情况下对组件进行测试。将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。

Context.Provider

jsx 复制代码
<MyContext.Provider value="" />

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化

Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 可以嵌套使用,里层的会覆盖外层的数据

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。从 Provider 到其内部 consumer 组件(contextType, useContext)的传播不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其组件跳过更新的情况下也能更新。

生产消费者模型

组件只要定义了 contextType, 就是消费者,消费者可以订阅生产者,消费者可以随时响应状态的变化

context 可以无视中间组件的渲染,依然可以响应生产者数据的变化

通过新旧值检测来确定变化,使用了和 Object.is 相同的算法

js 复制代码
=0 === +0 // true
Object.is(-0, +0) // false
注意点
  • context 会根据引用标识来决定何时进行渲染(本质是 value 属性值的比较)
  • 当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染
  • 当每一次 Provider 重渲染时,由于 value 属性总是被赋值为新的对象,以下的代码会重新渲染所有的 consumer 组件
js 复制代码
class App extends React.Component {
  render() {
    return (
      <MyContext.Provider value={{ something: "something" }}>
        <Toolbar />
      </MyContext.Provider>
    );
  }
}
  • 为了防止这种情况,将 value 状态提升到父节点的 state 里
js 复制代码
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: { something: "something" },
    };
  }
  render() {
    return (
      <MyContext.Provider value={{ something: "something" }}>
        <Toolbar />
      </MyContext.Provider>
    );
  }
}
原理
  1. 将初始值存储在 context._currentValue
  2. 创建 Context.Provider 和 Context.Consumer 对应的 ReactElement 对象

在 fiber 树渲染时,通过不同的 workInProgress.tag 处理 Context.Provider 和 Context.Consumer 类型的节点。

在 React 中提供了 3 种消费 Context 的方式

  1. 直接使用 Context.Consumer 组件(也就是上面 createContext 时创建的 Consumer)
  2. 类组件中,可以通过静态属性 contextType 消费 Context
  3. 函数组件中,可以通过 useContext 消费 Context

这三种方式内部都会调用 prepareToReadContext 和 readContext 处理 Context。prepareToReadContext 中主要是重置全局变量为 readContext 做准备。

readContext 的核心逻辑:

  1. 构建 contextItem 并添加到 workInProgress.dependencies 链表(contextItem 中保存了对当前 context 的引用,这样在后续更新时,就可以判断当前 fiber 是否依赖了 context ,从而判断是否需要 re-render)
  2. 返回对应 context 的 _currentValue 值

更新 Context

当触发 Context.Provider 的 re-render 时,重新走 updateContextProvider 中更新的逻辑

核心逻辑:

  1. 从 ContextProvider 的节点出发,向下查找所有 fiber.dependencies 依赖当前 Context 的节点
  2. 找到消费节点时,从当前节点出发,向上回溯标记父节点 fiber.childLanes,标识其子节点需要更新,从而保证了所有消费 3. 了该 Context 的子节点都会被重新渲染,实现了 Context 的更新
总结

在消费阶段,消费者通过 readContext 获取最新状态,并通过 fiber 关联当前 Context

在更新阶段,从 ContextProvider 节点出发查找所有消费了该 context 的节点

相关推荐
勿语&28 分钟前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
黄尚圈圈30 分钟前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水1 小时前
简洁之道 - React Hook Form
前端
正小安4 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch5 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光5 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   5 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   5 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web5 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常5 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式