过时的 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
属性来设置 Button
和 Message
组件的样式。使用 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 的生产者)添加 childContextTypes
和 getChildContext
,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
组件向下传递一些信息,每一个 Link
和 Route
能够与包含它的 Router
通信。
在你构建类似的 API 的组件之前,先考虑是否有更简洁的方式。例如,如果你愿意的话,你可以将整个 React 组件作为 props。
在生命周期方法中引用 Context
本节记录一个过时的 API。查看 新的 API。
如果一个组件内定义了 contextTypes
,下面的 生命周期方法 会接收一个额外参数,就是 context
对象:
constructor(props, context)
componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componentWillUpdate(nextProps, nextState, nextContext)
注意:
从 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。这篇博客文章很好地解释了为何会出现此类问题,以及你该如何规避它。