当前位置: 首页 > 文档资料 > React 高级教程 >

1.10 上下文

优质
小牛编辑
131浏览
2023-12-01

在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

假定有下面的结构:

class Button extends React.Component {
  render() {
    return (
      <button style={{'{{'}}background: this.props.color}}>
        {this.props.children}
      </button>
    );
  }
}

class Message extends React.Component {
  render() {
    return (
      <div>
        {this.props.text} <Button color={this.props.color}>Delete</Button>
      </div>
    );
  }
}

class MessageList extends React.Component {
  render() {
    const color = "purple";
    const children = this.props.messages.map((message) =>
      <Message text={message.text} color={color} />
    );
    return <div>{children}</div>;
  }
}

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

class Button extends React.Component {
  render() {
    return (
      <button style={{'{{'}}background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
}

Button.contextTypes = {
  color: React.PropTypes.string
};

class Message extends React.Component {
  render() {
    return (
      <div>
        {this.props.text} <Button>Delete</Button>
      </div>
    );
  }
}

class MessageList extends React.Component {
  getChildContext() {
    return {color: "purple"};
  }

  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

MessageList.childContextTypes = {
  color: React.PropTypes.string
};

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

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

父子耦合

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

import { BrowserRouter as Router, Route, Link } from 'react-router-dom';

const BasicExample = () => (
  <Router>
    <div>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/topics">Topics</Link></li>
      </ul>

      <hr />

      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/topics" component={Topics} />
    </div>
  </Router>
);

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

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

在生命周期函数中使用Context

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

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

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

const Button = ({children}, context) =>
  <button style={{'{{'}}background: context.color}}>
    {children}
  </button>;

Button.contextTypes = {color: React.PropTypes.string};

更新Context

别这么做!

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

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

class MediaQuery extends React.Component {
  constructor(props) {
    super(props);
    this.state = {type:'desktop'};
  }

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

  componentDidMount() {
    const checkMediaQuery = () => {
      const type = window.matchMedia("(min-width: 1025px)").matches ? 'desktop' : 'mobile';
      if (type !== this.state.type) {
        this.setState({type});
      }
    };

    window.addEventListener('resize', checkMediaQuery);
    checkMediaQuery();
  }

  render() {
    return this.props.children;
  }
}

MediaQuery.childContextTypes = {
  type: React.PropTypes.string
};

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