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 的节点

相关推荐
匹马夕阳11 分钟前
基于Canvas和和原生JS实现俄罗斯方块小游戏
javascript·canva可画
m0_6161884912 分钟前
Vue3 中 Computed 用法
前端·javascript·vue.js
六个点15 分钟前
图片懒加载与预加载的实现
前端·javascript·面试
weixin_4607838721 分钟前
Flutter解决TabBar顶部页面切换导致页面重载问题
android·javascript·flutter
Patrick_Wilson37 分钟前
🔥【全网首篇】30分钟带你从0到1搭建基于Lynx的跨端开发环境
前端·react.js·前端框架
逍遥客.39 分钟前
uniapp对接打印机和电子秤
javascript·vue.js·uni-app
小沙盒39 分钟前
godot在_process()函数实现非阻塞延时触发逻辑
javascript·游戏引擎·godot
Moment44 分钟前
前端 社招 面筋分享:前端两年都问些啥 ❓️❓️❓️
前端·javascript·面试
Moment44 分钟前
一坤时学习 TS 中的装饰器,让你写 NestJS 不再手软 😏😏😏
前端·javascript·面试
子洋44 分钟前
AnythingLLM + SearXNG 实现私有搜索引擎代理
前端·人工智能·后端