此文章是翻译Context这篇React(版本v16.2.0)官方文档。

Context

注意:

从React v15.5起,React.PropTypes 已经被移动到一个不同的包,我们建议使用prop-types来定义contextTypes

我们提供了a codemod script 来自动转换。

使用React ,很容易追踪通过你的React 组件的流动的数据。但你察看一个组件时,你可以看到那一个props 被传入,这使得你的应用很容易推理。

在一些情况下,你希望通过组件树传递数据,而不需要在每一级手动通过props 进行传递。你可以在React 中直接使用强大的“context” API 实现它。

Why Not To Use Context

绝大多数应用不需要使用context。

如果你想要你的应用是健壮的,就不能使用context。这是一个实验性API并且它很可能在未来React 的发布版本中改变。

如果你不熟悉像ReduxMobX 这种state 管理库,不要使用context。在许多实际应用中,这些库以及和它们的React 绑定是一个很好的管理和许多组件相关的state。更有可能的是Redux 和context 相比是一个很好的解决方案。

如果你不是一个经验丰富的React 开发者,不要使用context。这通常有只需要使用props 和state 就能实现这些功能。

如果你坚持使用context 而不管这些警告,尝试将你使用的context 独立在一小块区域中避免直接使用这些context API,以便当这些API 改变后便于升级。

How To Use Context

假设你有一个如下的结构:

{% raw %}

  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. }

{% endraw %}

在这个例子中,我们以适当的方式手动得传入一个color props 到ButtonMessage 组件中。使用context,我们可以通过这个树自动传入:

{% raw %}

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

{% endraw %}

通过添加childContextTypesgetChildContextMessageList(context 提供者),React 可以自动向下传递信息并且子树中的任何组件(这个例子中的Button)都可以通过定义contextTypes 去访问它。

如果contextTypes 没有被定义,那么这个context 将是一个空对象。

Parent-Child Coupling

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 exactly pattern="/" component={Home} />
  12. <Route pattern="/about" component={About} />
  13. <Route pattern="/topics" component={Topics} />
  14. </div>
  15. </Router>
  16. );

Router 组件向下传递一些信息,每一个LinkRoute 都可以回传到包含容器Router

在你使用类似的API 构建组件之前,考虑是否有一个更好的替代方案。例如,如果你喜欢你可以将整个React 组件作为props 传入。

Referencing Context in Lifecycle Methods

如果contextTypes 在component 中被定义,下面这些lifecycle methods 将会接受一个额外的context 对象作为参数:

注意:

自从React 16,componentDidUpdate 不再接受prevContext

Referencing Context in Stateless Functional Components

无状态的函数式组件也可以引用context 如果contextTypes 作为函数的属性被定义。下面代码展示一个无状态的函数式Button 组件。

{% raw %}

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

{% endraw %}

Updating Context

不要做它。

React 有一个可以更新context 的API,但是它会从根本上造成破坏,所以你不应该使用它。

当state 和props 改变时,就会调用getChildContext 函数。为了更新context 中的数据,使用this.setState 触发本地state 更新。这将会产生新的context 并且改变会被子节点收到。

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

问题是,如果组件提供的context 值发生改变,后代使用这个值将不会更新如果中间件的父的shouldComponentUpdate 返回false。使用context 这个组件是完全失去控制,所以这里基本没有办法依赖更新context。这篇文章 解释了为什么这是一个问题并且你怎么避免它。