过时的 Context

注意:

过时的 context API 会在未来的主要版本中被移除。 使用 16.3 版本中引入的 新的 context API。 过时的 API 将会继续在所有 16.x 版本中工作。

如何使用 Context

本节记录一个过时的 API。查看 新的 API

假设你有这样一个结构:

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,我们可以通过组件树自动传递属性:

import PropTypes from 'prop-types';

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

Button.contextTypes = {  color: 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: PropTypes.string};

通过给 MessageList(context 的生产者)添加 childContextTypesgetChildContext,React 自动向下传递信息,子树上的所有组件(在这个例子中是 Button)可以通过定义 contextTypes 来访问 context。

如果 contextTypes 没有被定义,context 就会是个空对象。

注意:

自从 React 15.5 版本之后,React.PropTypes 已经转移到了另一个包。请改用 prop-types 来定义 contextTypes

我们提供了 一个 codemod 脚本 来自动转换。

父-子 结合

本节记录一个过时的 API。查看 新的 API

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 组件向下传递一些信息,每一个 LinkRoute 能够与包含它的 Router 通信。

在你构建类似的 API 的组件之前,先考虑是否有更简洁的方式。例如,如果你愿意的话,你可以将整个 React 组件作为 props。

在生命周期方法中引用 Context

本节记录一个过时的 API。查看 新的 API

如果一个组件内定义了 contextTypes,下面的 生命周期方法 会接收一个额外参数,就是 context 对象:

注意:

从 React 16 开始,componentDidUpdate 不再接收 prevContext

在函数组件中引用 Context

本节记录一个过时的 API。查看 新的 API

只要 contextTypes 被定义为函数的一个属性,函数组件也能够引用 context。下面的代码展示了一个函数组件写法的 Button 组件。

import PropTypes from 'prop-types';

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

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

更新 Context

本节记录一个过时的 API。查看 新的 API

不要这样做。

React 有一个 API 可以更新 context,但它基本上是不靠谱的,你不应该使用它。

当 state 或者 props 改变的时候,getChildContext 函数就会被调用。为了更新 context 里的数据,使用 this.setState 触发当前 state 的更新。这样会产生一个新的 context 并且子组件会接收到变化。

import PropTypes from 'prop-types';

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: PropTypes.string
};

问题是,如果组件提供的一个 context 发生了变化,而中间父组件的 shouldComponentUpdate 返回 false,那么使用到该值的后代组件不会进行更新。使用了 context 的组件则完全失控,所以基本上没有办法能够可靠的更新 context。这篇博客文章很好地解释了为何会出现此类问题,以及你该如何规避它。