What is Context
今天在学习styled-components的Theming
时,关于styled-components
对主题的实现与管理上提到,主要应用到了react
的context API,所以好好研读了一下官方文档,对该API做了如下记录。
什么是Context
当我们使用React
时,很容易的通过观察组件的props
来跟踪组件间的数据流流向,这种跟踪观察方式也让我们很容易的去理解组件。
而有的时候,我们不想让一个props
从最外层,通过组件一层一层的传递到目标组件上,这时就可以通过context
来直接实现我们希望的操作。
怎样使用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
手动的方式传给了Button
,这期间穿越了Message
,而对Message
本身没有什么用。如果用context
的话,可以直接给到Button
组件上,如下:
const PropTypes = require('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
,可以实现在该组件子结构下的所有组件(e.g. Button)直接通过定义contextTypes
来获取。
如果未定义contextTypes
的话,context
是一个空对象。
可获取Context对象的勾子函数
一旦组件定义了contextTypes
以后,以下的勾子中就会得到一个附加的参数——context
对象:
- constructor(props, context)
- componentWillReceiveProps(nextProps, nextContext)
- shouldComponentUpdate(nextProps, nextState, nextContext)
- componentWillUpdate(nextProps, nextState, nextContext)
- componentDidUpdate(prevProps, prevState, prevContext)
无状态组件获取Context方法
无状态组件同样可以通过给函数定义contextTypes
属性的方式,让组件拥有获取context
的能力,例如:
const PropTypes = require('prop-types');
const Button = ({children}, context) =>
<button style={{background: context.color}}>
{children}
</button>;
Button.contextTypes = {color: PropTypes.string};
Context的更新
不要更新Context!
React
虽然有提供关于更新context
的API,但不建议去使用。
如果想用的话,可以看下面的这个例子。getChildContext
方法会在state
或props
更新时被调用,可以通过局部状态的更新进而来更新context
。当context
更新后,所有的子组件都能接到新值。
const PropTypes = require('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
更新了,其下使用该context
的子组件可能因为某个父组件的shouldComponentUpdate
返回false
而不做状态更新。这就完全不符合通过使用context
来控制组件状态更新的初衷,所以证明使用context
来管理组件状态不太靠谱。
这里有篇博客关于介绍如何安全的使用context
的。
不建议使用Context
绝大多数的应用程序是不需要使用context
的。
如果你想要你的应用稳定,就不要使用它,这是一个实验性的API,在未来的版本更新中很有可能会被弃掉。
context最好的使用场景是隐式的传入登录的用户,当前的语言,或者主题信息。要不然所有这些可能就是全局变量,但是context让你限定他们到一个单独的React树里。
如果项目对数据管理较为复杂,推荐使用类似于redux或mobX这样的状态管理库,而不要使用context
。
记录的过程是一种成长,欢迎大家关注我的github。