React的Context简单介绍

183 阅读7分钟

此次基于react文档15.8版本

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

官网上的说明: 

1.Context提供了一个无需为每层组件手动添加props, 就可以在组件树中进行数据传递的方法。 

2.官网并没有给出明确Context是什么的说明, 只是给出了使用场景。(但可以打印一下组件的实例, 可以看出Context对象在组件实例中,展示形式为this.context= {}, Context对象被绑在父类继承过来的this上。但是读写context有多种方式,会在下边介绍) 


3.官网并不推荐使用Context,因为会使得组件的复用性变差。

何时使用context?

import React from "react";
class GrandPar extends React.Component {    
    constructor(props) {        
        super(props);        
        this.state = {};    
    }
    render() {        
        let component = "GrandPar";        
        return (            
            <div                 
                style = {                    
                {background:"#eee",                     
                border:"1px solid #000",                    
                width:700,                     
                alignItems:'center',                     
                display:'flex',                     
                flexDirection:'column'}                
                }            
            >                 
                这里是GrandPar组件,本组件直接包含+间接包含了Par & Son组件<br/>                
                <Par component = {component}/>            
            </div>        
        );    
    }}
class Par extends React.Component {    
    constructor(props) {        
        super(props);        
        this.state = {};    
    }
    render() {       
    /**        
    *  此处注释的是与render种 <Son component = {component}/> 相对应,        
    *  代表向子组件传送props,可以有多种方式,一种是具体的属性名, 另一种是解构        
    */
        // let {        
        //     component        
        // } = this.props;        
        return (            
            <div style = {                
                {background:"rgb(204, 133, 133)",                 
                border:"1px solid #000",                 
                width:500,                 
                alignItems:'center',                 
                display:'flex',                 
                flexDirection:'column'}}            
            >                
                这里是Par组件直接包含Son组件<br/>                
                {/* <Son component = {component}/> */}                
                <Son {...this.props}/>            
            </div>        
            );    
    }}

class Son extends React.Component {    
    constructor(props) {       
        super(props);        
        this.state = {};    
    }
    render() {        
        let {            
            component        
        } = this.props;        
        console.log(this.props, "son组件的props包含了什么")        
        return (            
            <div style = {{background:"rgb(214, 198, 198)", border:"1px solid #000", width:300}}>                
                这里是Son组件<br/>                
                component是从最上层的组件传下来的<br/>                
                <br/>                   
                {`component的值到底是多少呢?答案是: ${component}`}            
            </div>        
        );    
    }}

export default GrandPar;


由上代码可知当前是三层组件树,当Son组件 想使用GrandPar组件的component属性, 就必须要经过Par组件的传递(层层传递),Son组件才可以获取到component属性。 

此时我们就可以使用context,是为了避免属性的层层传递就可以在Son组件中获取到GrandPar的属性, 无论组件树有多少层,只要是绑定了context对象的组件都可以获取到component属性, 如果没绑定就算包含在provider组件中, 也是获取不到的component属性值的, 除非使用Consumers, 就可以不用绑定context对象, 就可以获取到对应的父组件的值。context绑定与使用在下边会详解。   

创建context的方式

 const MyContext = React.createContext(defaultValue);

注意: 

1.defaultValue只有当组件所处的树中没有匹配到Provider时, 其defaultValue参数才会生效。

2.将undefined传递给Provider时, 离自身最近的那个匹配Provider的组件的defaultValue并不会生效。

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


context的传递方式 ProvderReact组件

<MyContext.Provider value = {'想要传递的值'} />

import React from "react";export const themes = {    value :"原来"};const myContext = React.createContext({value:"默认"});class Base extends React.Component {    constructor(props, context) {        super(props, context);        console.log(props, context, "constructor")        this.state = {};    }    componentWillMount(){        console.log(this, "componentWillMount")    }    componentDidMount(){        console.log(this, "componentDidMount")    }    componentWillReceiveProps(nextProps, nextContent){        console.log(this, nextProps, nextContent, "componentWillReceiveProps")    }    shouldComponentUpdate(newProps, newState, nextContent) {        console.log(this,newProps, newState, nextContent,"componentWillReceiveProps")        return true;    }    componentWillUpdate(newProps, newState, nextContent) {        console.log(newProps, newState, nextContent, "componentWillUpdate");    }    componentDidUpdate(prevProps, prevState, preConext) {        console.log(prevProps, prevState, preConext, 'componentDidUpdate')    }       render() {        return (            <div>                    {this.context.value}                <button {...this.props} onClick = {this.context.onChange}>快来点我</button>            </div>        );    }}Base.contextType  = myContext;
class ConextComponent extends React.Component {    constructor(props, context) {        super(props, context);        console.log(props, context)        this.onChange = ()=>{            this.setState({                value:"改变后"            }, ()=>{                console.log(this.state.value, "onChange")            })        }        this.state = {            value :'未修改前',            onChange : this.onChange        };    }    componentWillMount(){        console.log(this, "ConextComponent ------- componentWillMount")    }    componentDidMount(){        console.log(this, "ConextComponent -------componentDidMount")    }    componentWillReceiveProps(nextProps, nextContent){//         console.log(this, nextProps, nextContent, "ConextComponent -------componentWillReceiveProps")    }    shouldComponentUpdate(newProps, newState, nextContent) {        console.log(this,newProps, newState, nextContent,"ConextComponent -------componentWillReceiveProps")        return true;    }    componentWillUpdate(newProps, newState, nextContent) {        console.log(newProps, newState, nextContent, "ConextComponent -------componentWillUpdate");    }    componentDidUpdate(prevProps, prevState, preConext) {        console.log(prevProps, prevState, preConext, 'ConextComponent -------componentDidUpdate')    }      onChange = () =>{        this.setState({            value:"改变后"        })    }    render() {        return (            <div>                   <myContext.Provider value = {self.state}>                    <div onClick={this.onChange}></div>                    <Base/>                </myContext.Provider>            </div>        );    }}export default ConextComponent ;

由代码可见, 子组件Base的context是父组件ContextComponent通过MyContext.Provider上属性value去传递的。此处的value属性对应着子组件的context对象。在子组件中需通过this.context或者生命周期的context参数获取context对象的值。


上图是 :子组件的componentWillMount生命周期中获取到的context对象的值。

点击‘快来点我’按钮之后, context改变。


上图是 :子组件的获取到的context对象改变后的值。

注意 : MyContext.Provider上的属性, value(固定属性名称), 它的作用就是:子组件是通过这个属性获取到context对象传递的值。

每一个Context对象都会返回一个ProviderReact组件,此ProviderReact组件上绑定着value属性, 此组件允许消费组件(也就是子组件)订阅context的变化, 那这个变化的值, 就是通过value传递。

ProviderReact的特征:

1.ProviderReact组件接受一个value属性对应着一个context值, 然后传递给包含在ProviderReact组件内层的组件。

2.一个ProviderReact组件可以和多个组件有对应关系。

3.多个ProviderReact组件也可以嵌套使用, 里层的会覆盖外层的数据。

4.当Provider的value值发生变化时, 它内部的所有组件都会重新渲染。

5.Provider及其内部组件都不受制于shouldComponentUpdate生命周期。因此当内部组件在其祖先组件退出更新的情况下也能更新。无论shouldComponentUpdate返回trueOrfalse, 都无影响(自己打印shouldComponentUpdate方法可见, 使用了Provider包含的组件,并不会走到shouldComponentUpdate方法里。)。

6.在生命周期中, 如何获取context对象的值?, 在现有的生命周期中, 在最后一个参数的位置加上nextContext,或者使用this.context。二者选其一。 (前提是此组件绑定了context对象。)

7.对于面向函数的无状态组件(也就是函数组件), 可以通过函数的参数直接访问组件的Context对象的值。

const SatetComponent = (props, context) =>{
......
}

将context对象绑定到组件上  (Class.contextType)第一种获取context的值的方式

export default MyComponent extends Reacr.Component{
    coustructor(props){
        super(props);
    }
    render(){
        return (<div></div>)
    }
}
const MyContext = React.createContext('defaultValue')
MyComponent.contextType = MyContext;

已经挂在MyComponent 组件上的contextType属性会被重新赋值为一个由React.createContext()创建的Context对象。这便可以通过this.context来使用离ProviderReact最近的Context上的那个值。也可以在任何一个生命周期中访问到它。也就是此被绑定了context对象组建的任何地方, 都可以获取到context的值。


ConsumerReact组件第二种获取context的值的方式

<myContext.Consumer>
    {(value1,value2, value3...) => {return (组件)}}
</myContext.Consumer>

class BaseConsumer extends React.Component {    constructor(props, context) {        super(props, context);        console.log(props, context, "constructor")        this.state = {};    }
    render() {        return (            <myContext.Consumer>                   {({value, onChange})=>{                    return (                        <div>                            {value}                            <button {...this.props} onClick = {onChange}>快来点我</button>                            </div>                    )                }}            </myContext.Consumer>        );    }}
BaseConsumer.contextType  = myContext;class ConextComponentConsutomer extends React.Component {    constructor(props, context) {        super(props, context);        console.log(props, context);        this.onChange = ()=>{            this.setState({                value:"改变后"            })        }        this.state = {            value :'未修改前',            onChange : this.onChange        };    }        render() {        let self = this;        return (            <div>                   <myContext.Provider value = {self.state}>                    <div onClick={self.onChange}></div>                    <BaseConsumer/>                </myContext.Provider>            </div>        );    }}export default ConextComponentConsutomer ;

ConsumerReact中渲染的是函数组件。 

1.如果Consumer所在的组件上没有使用MyComponent.contextType = MyContext;那么就单纯的从函数组件的参数中去获取。

2.如果绑定了MyComponent.contextType = MyContext;, 那么this.context和函数组件参数中, 任选一种方式获取context对象值。


生命周期与Context的关系

修改context之后


备注:代码依旧是上边介绍provider的代码。

由生命周期的打印可见, 只有constructor、componentWillMount、componentDidMount、componentWillReceiveProps、componentWillUpdate, 中在现有的参数中,再加一个参数, 便是context, 并且可以获取到正确的context的值。

注意此代码里明明有shouldComponentUpdate生命周期函数, 但为什么打印里没有, 是因为, context并不受shouldComponentUpdate函数控制。所以并不会触发shouldComponentUpdate函数。


总体注意事项:因为context会使用参考标识来决定何时进行渲染, 这里可能会有一些陷阱,当provider的父组件进行重新渲染时, 可能会在consumer组件中发生意外的渲染。为防止这种情况, 将value状态提升到父节点的state里。