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 读取

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
function ThemedButton() {
  return (
    <ThemeContext.Consumer>
      {(value) => <button>{value}</button>}
    </ThemeContext.Consumer>
  );
}

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

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

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

API

React.createContext

const MyContext = React.createContext(defaultValue);

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

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

Context.Provider

<MyContext.Provider value="" />

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

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

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

生产消费者模型

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

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

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

=0 === +0 // true
Object.is(-0, +0) // false
注意点
  • context 会根据引用标识来决定何时进行渲染(本质是 value 属性值的比较)
  • 当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染
  • 当每一次 Provider 重渲染时,由于 value 属性总是被赋值为新的对象,以下的代码会重新渲染所有的 consumer 组件
class App extends React.Component {
  render() {
    return (
      <MyContext.Provider value={{ something: "something" }}>
        <Toolbar />
      </MyContext.Provider>
    );
  }
}
  • 为了防止这种情况,将 value 状态提升到父节点的 state 里
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 的节点

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-03 10:00:10       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-03 10:00:10       106 阅读
  3. 在Django里面运行非项目文件

    2024-05-03 10:00:10       87 阅读
  4. Python语言-面向对象

    2024-05-03 10:00:10       96 阅读

热门阅读

  1. ffmpeg 转换es流成为ps流

    2024-05-03 10:00:10       33 阅读
  2. 揭秘FastStone Capture

    2024-05-03 10:00:10       45 阅读
  3. Nacos在微服务架构中如何支持服务发现和注册

    2024-05-03 10:00:10       40 阅读
  4. 区分Vue2和Vue3的配置读取(附Demo)

    2024-05-03 10:00:10       32 阅读
  5. 高可用系列四:loadbalancer 负载均衡

    2024-05-03 10:00:10       31 阅读
  6. 愚安科技安全工程师面经:

    2024-05-03 10:00:10       32 阅读
  7. Windows 系统运维常用命令

    2024-05-03 10:00:10       28 阅读
  8. 什么是Vuex它的作用是什么怎么用

    2024-05-03 10:00:10       36 阅读
  9. 新人天上下凡尘

    2024-05-03 10:00:10       30 阅读