React从入门到精通系列之(20)用上下文管理应用

二10、用上下文管理应用

使用React能够很容易经过React组件跟踪数据流。 当你看到一个组件,你就能够看到哪些props正在传递,这使得你的应用很容易知道在作什么。javascript

在某些状况下,你但愿经过组件树传递数据,而不是在每一个级别的中组件手动传递props,你能够直接在React中使用强大的“context”来作到这一点。java

为何不去使用Context

绝大多数的应用不须要直接使用Context。react

若是你但愿你的应用是稳定的,那么就不要使用Context。 由于这是一个实验性质的API,它可能会在将来的React版本中移除。dom

若是你不熟悉state管理库如ReduxMobX,不要使用Context。 对于许多实际的应用,这些state管理库和React一块儿使用来管理那些与组件相关的state一个很不错的选择。 不少状况下使用Redux就能够解决你的问题,而不是使用Context。函数

若是你不是一个有经验的React开发人员,不要使用Context。 使用propsstate来实现功能是一个更好的方法。ui

尽管有上面这些警告你还坚持使用Context,那么请将Context单独隔离到一个小区域中,并尽量地避免直接使用Context,以便在这个API更改时应用能更容易升级。this

如何使用Context

假设你有一个结构:code

import React from 'react';
import ReactDOM from 'react-dom';

class MyButton extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const style = {backgroundColor: this.props.color};
        return <button style={style}>{this.props.children}</button>;
    }
}
class Message extends React.Component {
    render() {
        return (
            <div>
                {this.props.text}
                <MyButton color={this.props.color}>删除</MyButton>
            </div>
        )
    }
}
class MessageList extends React.Component {
    render() {
        const color = 'red';
        const children = this.props.messages.map(msg => <Message text={msg} color={color}/>);
        return <div>{children}</div>;
    }
}
const messages = ['zhangyato', 'Re: zhangyatao', 'Re:Re:zhangyatao'];
ReactDOM.render(
    <MessageList messages={messages}/>,
    document.getElementById('root')
);

在这个例子中,咱们手动传入一个color props进行传递,以便适当地设置MyButtonMessage组件的样式。 使用Context,咱们可让color自动在组件树中传递:component

import React from 'react';
import ReactDOM from 'react-dom';

class MyButton extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const style = {backgroundColor: this.context.color};
        return <button style={style}>{this.props.children}</button>;
    }
}
MyButton.contextTypes = {
    color: React.PropTypes.string.isRequired
};
class Message extends React.Component {
    render() {
        return (
            <div>
                {this.props.text}
                <MyButton>删除</MyButton>
            </div>
        )
    }
}
class MessageList extends React.Component {
    getChildContext() {
        return {color: 'red'};
    }

    render() {
        const children = this.props.messages.map(msg => <Message text={msg}/>);
        return <div>{children}</div>;
    }
}
MessageList.childContextTypes = {
    color: React.PropTypes.string.isRequired
};
const messages = ['zhangyato', 'Re: zhangyatao', 'Re:Re:zhangyatao'];
ReactDOM.render(
    <MessageList messages={messages}/>,
    document.getElementById('root')
);

经过向MessageList(Context提供者)添加childContextTypesgetChildContext,React就会自动传递Context信息,子组件树中的任何组件(Button)均可以经过定义contextTypes来访问它。对象

若是未定义contextTypes,那么Context将是一个空对象。

父子组件之间的耦合

Context还可让你实现父组件和子组件之间的交互。 例如,以这种方式工做的一个比较知名的库为React Router V4

const RouterExample = () => {
    <Router>
        <div>
            <ul>
                 <li><Link to="/">主页</Link></li>
                 <li><Link to="/recyclebin">回收站</Link></li>
                 <li><Link to="/timeline">图片</Link></li>
            </ul>

            <hr />

            <Match exactly pattern="/" component={Home} />
            <Match pattern="/recyclebin" component={RecycleBin} />
            <Match pattern="/timeline" component={TimeLine} />

        </div>
    </Router>
}

经过从RouterExample组件传递一些信息,每一个LinkMatch能够回传到包含的Router中。

在使用相似于此的API进行构建组件以前,请考虑是否有更好的替代品。 例如,你能够传递整个React组件做为props。

生命周期方法中使用Context

若是在一个组件中定义了contextTypes,则如下生命周期方法将多接收个参数,就是Context对象:

  • constructor(props, context)

  • componentWillReceiveProps(nextProps, nextContext)

  • shouldComponentUpdate(nextProps, nextState, nextContext)

  • componentWillUpdate(nextProps, nextState, nextContext)

  • componentDidUpdate(prevProps, prevState, prevContext)

在无状态功能性组件中使用Context

若是contextTypes被定义为无状态功能性组件的属性,无状态功能性组件也可以引用Context。 下面的代码显示了被写成一个无状态的功能组件的MyButton组件。

function MyButton(props, context) {
     const children = props.children;
     return (
         <button style={{backgroundColor: context.color}}>
             {children}
         </button>
     );
}
MyButton.contextTypes = {
    color: React.PropTypes.string.isRequired
};

更新Context

千万不要这么作!!!

React有一个API来更新Context,但它从根本上破坏了Context,因此你不该该使用它。

当state或props改变时,getChildContext函数将被调用。 为了更新Context中的数据,使用this.setState()触发组件内的state更新。 这也将触发一个新的Context,而且Context改变的内容也会被子组件接收到。

import React from 'react';
import ReactDOM from 'react-dom';

class MediaType extends React.Component {
    render() {
        return <div>type is {this.context.type}</div>
    }
}
MediaType.contextTypes = {
    type: React.PropTypes.string
};
class MediaQuery extends React.Component {
    constructor(props) {
        super(props);
        this.state = {type: 'PC端'};
        this.checkMediaQuery = this.checkMediaQuery.bind(this);
    }

    getChildContext() {
        return {type: this.state.type}
    }

    checkMediaQuery() {
        let type = window.matchMedia('<code>zhangyatao</code>(max-width: 760px)').matches ? '移动端' : 'PC端';
        this.setState({type: type});
    }

    componentDidMount() {
        window.addEventListener('resize', this.checkMediaQuery, false);
        this.checkMediaQuery();
    }

    render() {
        return <div>{this.props.children}</div>;
    }
}
MediaQuery.childContextTypes = {
    type: React.PropTypes.string
};
ReactDOM.render(
    <MediaQuery>
        <MediaType />
    </MediaQuery>,
    document.getElementById('root')
);

问题是,Context的值经过组件更新来提供,若是中间的父组件在shouldComponentUpdate()返回false,那么使用该Context值的后代组件永远不会被更新。使用Context彻底不受组件的控制,因此基本上没有办法可靠地更新Context。 这篇文章很好的解释了为何这是一个问题,以及你应该如何摆脱它。