关于react-context的官方文档
https://reactjs.org/docs/context.html

起因

很多同学认为一旦父子组件之间层级过深,多层级通讯时候第一反应就是要用vuex和redux,但往往杀鸡用了宰牛刀。但是vue和react中提供了很多方法去达到数据共享的效果,而不必显式地通过组件树的逐层传递 props。
比如在vue中provide 和 inject

1、这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
2、provide提供数据,多层子组件 向上层寻找,只要找到 就不在向上层寻找了。
3、inject 向子组件注入数据;

vue中的mixin和extend也可以做到相关功能,这里就不做详细赘述,需要了解的同学可以自行去观看。
下面回到本篇文章的正题react-context,React官网的高级指引中指出了Context,优雅的解决这个问题。

context含义

前端同学都知道在js中的context指的是执行上下文,this指向谁,谁就是当前的执行上下文。

而react-context是什么呢?react-context官网中第一句就给出了解释:

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
意思便是context能够不必显式地通过组件树的逐层传递 props,可以跨层级进行数据传递。

其实质为跨层级的组件搭建一座桥梁。

context Api

React context的API有两个版本,React16.x之前的是老版本的context,之后的是新版本的context。

先简单说下老版本,之前也是实验性的存在,我觉得不太好用。生产者需要声明childContextTypes对象,对类数据类型进行校验,不提供就会报错。
具体的就不细说,需要了解的同学自行查看。

下面回到16.x版本的context,Context是16.3.0正式确定的API

学习context首先需要了解其三个核心的api: React.createContext()、Provider(发布者)、Consumer(订阅者)

React.createContext()和Provider

createContext()创建一个 Context 对象。

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

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

当 Provider 的value值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于shouldComponentUpdate函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。

value的值自然是可以动态变更的,并且会传递给子组件中,点击变更value按钮会发现页面上的三个tomorrow变为三个yesterday。

import React, { useContext } from 'react'

const ThemeContext = React.createContext('today')
class App extends React.Component {
  state = {
    user: 'tomorrow'
  }

  change = () => {
    this.setState({
      user: 'yesterday'
    })
  }

  render() {
    const { user } = this.state
    // createContext默认值是“today”。
    // 无论多深,任何组件都能读取这个值。
    // 这里Provider 把value值 “tomorrow” 作为当前的值传递下去。
    return (
      <ThemeContext.Provider value={ user }>
        <div onClick={ this.change }>变更value</div>
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

这里是react-hook函数式写法,用到react-hook的API:useContext获取context的值
挂载在 class 上的contextType属性会被重赋值为一个由React.createContext()创建的 Context 对象。这能让你使用this.context来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中

function Toolbar() {
  const context = useContext(ThemeContext)
  return (
    <div>
      <div>{context}</div>
      <Head />
    </div>
  );
}

Consumer

通过ThemeContext的属性Consumer消费用户数据
Consumer的子组件必须是一个function,通过function的参数接收顶层传入的数据
任何订阅者(Consumer)都可以直接修改context,这会导致后续的订阅者获取到修改后的context值,但这显然是不可取的。
如果需要修改,应该统一由发布者(Provider)修改,也就是类似APP组件中变更value按钮

class Head extends React.Component {
  render() {
    return (
      <ThemeContext.Consumer>
        {
          context => (
            <div>
              <div>{this.context}</div>
              <Title />
            </div>
          )
        }
      </ThemeContext.Consumer>
    )
  }
}

最后的子组件显示context内容
context还有个Api:Context.displayName
想了解的同学可以去官网看一下

class Title extends React.Component {
  static contextType = ThemeContext
  render() {
    return (
      <div>
        {this.context}
      </div>
    )
  }
}

注意事项

在 provider 的父组件进行重渲染时,可能会在 Consumers 组件中触发意外的渲染。

下面例子:对value直接赋值的时候就会触发这种不必要的渲染

const ThemeContext = React.createContext();
class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value={ {name: '666'} }>
        <div onClick={ this.change }>变更value</div>
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

可以通过把value值放在state中解决这种问题

const ThemeContext = React.createContext();
class App extends React.Component {
  state = {
    name: '666'
  }

  change = () => {
    this.setState({
      user: 'yesterday'
    })
  }

  render() {
    const { user } = this.state
    return (
      <ThemeContext.Provider value={ user }>
        <div onClick={ this.change }>变更value</div>
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

似曾相识的context -- redux

有没有发现redux的注入和context的注入非常相似。
image.png
通过React Developer Tools在浏览器很容易发现
react-redux实际上是通过Provider组件和connect方法进行连接react和redux,那么他们到底本质上就是通过Context来传递数据
WeChatb37598c11cb3bb30ffb2ac7bd28f063c.png

以上就是个人对react-context学习的一点总结。context虽然轻便,但是官方还是没有大面积的去推广,还是存在些问题,毕竟和强大的react-redux对比,没有很好的管理体系,广泛认知度也比较低。但是凡事没有绝对好坏,只有适不适合。


西若枫
20 声望5 粉丝