Context

在React中,在React组件中很容易追踪数据流。当你观察组件时,你可以找出哪些属性(props)被传递,这使得你的应用非常容易理解。

在某些场景下,你想在整个组件树中传递数据,但却不想手动地在每一层传递属性。你可以直接在React中使用强大的context API解决上述问题。

为什么不要使用Context

绝大多数的应用程序不需要使用context

如果你希望使用应用程序更加稳定就不要使用context。这只是一个实验性的API并且可能在未来的React版本中移除。

如果你不熟悉React或者Mobx这类state管理库,就不要使用context。对于许多应用程序,上述库和state绑定是管理state不错的选择。Redux相比context是更好的解决方法。

如果你不是一个有经验的React开发者,就不要使用context。更好的方式是使用propsstate

如果你不顾这些警告仍然坚持使用context,尝试着将context的使用隔离在一个将小的范围内,并且在可能的情况下直接使用context,以便在API改变的时候进行升级。

如何使用Context

假定有下面的结构:

  1. class Button extends React.Component {
  2. render() {
  3. return (
  4. <button style={{'{{'}}background: this.props.color}}>
  5. {this.props.children}
  6. </button>
  7. );
  8. }
  9. }
  10. class Message extends React.Component {
  11. render() {
  12. return (
  13. <div>
  14. {this.props.text} <Button color={this.props.color}>Delete</Button>
  15. </div>
  16. );
  17. }
  18. }
  19. class MessageList extends React.Component {
  20. render() {
  21. const color = "purple";
  22. const children = this.props.messages.map((message) =>
  23. <Message text={message.text} color={color} />
  24. );
  25. return <div>{children}</div>;
  26. }
  27. }

在这个例子中,我们手动地传递color属性使得ButtonMessage设置正确的样式。使用context,我们可以自动在组件树中传递属性。

  1. class Button extends React.Component {
  2. render() {
  3. return (
  4. <button style={{'{{'}}background: this.context.color}}>
  5. {this.props.children}
  6. </button>
  7. );
  8. }
  9. }
  10. Button.contextTypes = {
  11. color: React.PropTypes.string
  12. };
  13. class Message extends React.Component {
  14. render() {
  15. return (
  16. <div>
  17. {this.props.text} <Button>Delete</Button>
  18. </div>
  19. );
  20. }
  21. }
  22. class MessageList extends React.Component {
  23. getChildContext() {
  24. return {color: "purple"};
  25. }
  26. render() {
  27. const children = this.props.messages.map((message) =>
  28. <Message text={message.text} />
  29. );
  30. return <div>{children}</div>;
  31. }
  32. }
  33. MessageList.childContextTypes = {
  34. color: React.PropTypes.string
  35. };

通过给MessageList添加childContextTypeschildContextTypes(context提供者),React自动地向下传递信息,任何子树(例如:Button)可以通过定义contextTypes访问到属性。

如果没有定义contextTypes,context将是一个空的object。

父子耦合

Context可以构建API使得父组价和子组件进行相互通信。例如:React Router V4工作机制如下:

  1. import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
  2. const BasicExample = () => (
  3. <Router>
  4. <div>
  5. <ul>
  6. <li><Link to="/">Home</Link></li>
  7. <li><Link to="/about">About</Link></li>
  8. <li><Link to="/topics">Topics</Link></li>
  9. </ul>
  10. <hr />
  11. <Route exact path="/" component={Home} />
  12. <Route path="/about" component={About} />
  13. <Route path="/topics" component={Topics} />
  14. </div>
  15. </Router>
  16. );

通过从Router中传递相关信息,Router中的每一个LinkRoute都可以与之通信。

在你构建包含类似于上述的API的组件之前,考虑是否有其他的更清晰的选择。例如,你可以传递整个React组件作为props传递。

在生命周期函数中使用Context

如果contextTypes在组件中定义,下列的生命周期函数将接受一个额外的参数:context对象

在无状态的函数式组件中使用Context

如果contextType被定义为函数的属性,无状态函数式组件也能够引用context。下面的代码演示了一个Button状态的函数式组件。

  1. const Button = ({children}, context) =>
  2. <button style={{'{{'}}background: context.color}}>
  3. {children}
  4. </button>;
  5. Button.contextTypes = {color: React.PropTypes.string};

更新Context

别这么做!

React有一个API更新context,但是它打破了基本流程,不应该使用。

getChildContext函数将会在每次state或者props改变时调用。为了更新context中的数据,使用this.setState触发本地状态的更新。这将触发一个的context并且数据的改变可以被子元素收到。

  1. class MediaQuery extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {type:'desktop'};
  5. }
  6. getChildContext() {
  7. return {type: this.state.type};
  8. }
  9. componentDidMount() {
  10. const checkMediaQuery = () => {
  11. const type = window.matchMedia("(min-width: 1025px)").matches ? 'desktop' : 'mobile';
  12. if (type !== this.state.type) {
  13. this.setState({type});
  14. }
  15. };
  16. window.addEventListener('resize', checkMediaQuery);
  17. checkMediaQuery();
  18. }
  19. render() {
  20. return this.props.children;
  21. }
  22. }
  23. MediaQuery.childContextTypes = {
  24. type: React.PropTypes.string
  25. };

问题在于,组件提供的context值改变,后代元素如果shouldComponentUpdate返回false那么context的将不会更新。这使得使用context的组件完全失控,所以基本上没有办法可靠的更新context@mweststrate/how-to-safely-use-react-context-b7e343eff076">这篇blog很好的解释了为什么这是一个问题并如果绕过它。