浅谈React的Context API

前言

上下文(Context) 提供了在组件之间共享这些值的方法,而没必要在树的每一个层级显式传递一个 prop 。javascript

通常状况下,在你没有绝对把握用好和必须的场景下,是不推荐使用的。java

可是咱们依然间接的使用着它,好比许多官方依赖在使用,如:react-redux, mobx-react,react-router。咱们须要它功能的时候,更可能是靠第三方依赖库就能实现,而不是本身手动写context。可是,依然须要理解它,对用它的一些依赖库源码理解颇有帮助。react

import React, { Component } from 'react';

class Grandpa extends Component {
    state = {
        info: 'GrandPa组件的信息'
    }
    render() {
        return (
            <div>
                <Father info={this.state.info}/>
            </div>
        );
    }
}

class Father extends Component {
    render() {
        return (
            <div>
                <Son info={this.props.info}/>
            </div>
        );
    }
}

class Son extends Component {
    render() {
        return (
            <div>
                我是Son组件,拿到信息为:{this.props.info}
            </div>
        );
    }
}

export default Grandpa;

最后页面输出:redux

我是Son组件,拿到信息为:GrandPa组件的信息

咱们会发现这样的缺点是一层一层传值,若是有更多层级和更多数据的话,会让代码看起来很不整洁,若是中间哪一个组件忘了传值基本就完了;并且Father组件也并无用到info值,只是将值传给Son组件。api

若是使用context,就能帮咱们解决这个层级不停传值的问题。react-router

context有旧版和新版之分,以React v16.3.0版本划分。ide

旧版Context API

咱们先来讲下旧版context API函数

将代码改为以下:this

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Grandpa extends Component {
    state = {
        info: 'GrandPa组件的信息'
    }
    getChildContext () {
        return { info: this.state.info }
    }
    render() {
        return (
            <div>
                <Father/>
            </div>
        );
    }
}
// 声明Context对象属性
Grandpa.childContextTypes  = {
    info: PropTypes.string
}

class Father extends Component {
    render() {
        return (
            <div>
                <Son/>
            </div>
        );
    }
}

class Son extends Component {
    render() {
        return (
            <div>
                我是Son组件,拿到信息为:{this.context.info}
            </div>
        );
    }
}
// 根据约定好的参数类型声明
Son.contextTypes  = {
    info: PropTypes.string
}
export default Grandpa;

对PropTypes类型检查,还能够写成下面这种写法code

class Grandpa extends Component {
    static childContextTypes = {
        info: PropTypes.string
    }
    state = {
        info: 'GrandPa组件的信息'
    }
    getChildContext () {
        return { info: this.state.info }
    }
    render() {
        return (
            <div>
                <Father/>
            </div>
        );
    }
}

在须要传出去的context值和须要接收context值都要进行类型检查判断。

正经常使用这个api可能没什么问题,但若是中间哪一个组件用到shouldComponentUpdate方法的话,就极可能出现问题。

三个组件的层级关系以下

<GrandPa>
    <Father>
        <Son/>
    </Father>
</GrandPa>

若是在GrandPa组件设置按钮点击能够更新info的值,即经过this.setState({info: '改变值'})方法
更新 Context:
那么

  • GrandPa组件经过 setState 设置新的 Context 值同时触发子组件从新render。
  • Father组件从新render。
  • Son组件从新render,并拿到更新后的Context。

假如在Father组件添加函数

shouldComponentUpdate () {
    return false
}

因为Father并不依赖任何值,因此咱们默认让它无需从新render。可是,这会致使Son组件也不会从新render,即没法获取到最新的 Context 值。

这样的不肯定性对于目标组件来讲是彻底不可控的,也就是说目标组件没法保证本身每一次均可以接收到更新后的 Context 值。这是旧版API存在的一个大问题。

而新版API解决了这个问题,咱们来看下新版API怎么写的

新版Context API

新版Context 新增了 creactContext() 方法用来建立一个context对象。这个对象包含两个组件,一个是 Provider(生产者),另外一个是 Consumer(消费者)。
  1. Provider 和 Consumer 必须来自同一个Context对象,即一个由 React.createContext()建立的Context对象。
  2. React.createContext()方法接收一个参数作默认值,当 Consumer 外层没有对应的 Provider 时就会使用该默认值。
  3. Provider 组件使用Object.is方法判断prop值是否发生改变,当prop的值改变时,其内部组件树中对应的 Consumer 组件会接收到新值并从新渲染。此过程不受 shouldComponentUpdete 方法的影响。
  4. Consumer 组件接收一个函数做为 children prop 并利用该函数的返回值生成组件树的模式被称为 Render Props 模式。

用法

  1. 首先建立一个context对象
React.createContext 方法用于建立一个 Context 对象。该对象包含 Provider 和 Consumer两个属性,分别为两个 React 组件。
const InfoContext = React.createContext('');
  1. 使用Provider组件生成一个context
class Grandpa extends Component {
    state = {
        info: 'GrandPa组件的信息'
    }
    render() {
        return (
            <InfoContext.Provider value={{ info: this.state.info }}>
                <Father/>
            </InfoContext.Provider>
        );
    }
}
  1. 使用Consumer组件获取context对象的值
class Son extends Component {
    render() {
        return (
            <InfoContext.Consumer>
            {
                value => {
                    return <div>我是Son组件,拿到信息为:{value.info}</div>
                }
            }
            </InfoContext.Consumer>
        );
    }
}