react context解析
laizimo opened this issue · 1 comments
前言
react很容易上手,这是真的!!!毕竟对于ES6的知识点熟知度高的话,开发普通的小应用,react不再话下。可是,你是否考虑过,应用中组件间props传递的问题。比方说,我们需要向一个子子子子组件中传递一个props,一层一层的传递是不是过于丑陋了一些呢!那么,你或许会说用redux吧,做一个全局的store管理,这样就不会有这么多的麻烦了。可是,redux本身就不容易上手,何况你的程序可能并没有需要用到它那么复杂。那么,解决办法只有一个——context。这个东西,说实话,并不好用,或许对于不熟悉的你或许会踩坑,那么今天,我们就来好好聊聊它吧。
正文
context是一个尝试性的API,官方也并不建议去使用它。因为它会使得整个程序并不稳定。
首先,我们来尝试一下context的使用。
我们先来模拟一个列表应用,整个列表应用包含、、、
在不使用context的情况下:
class Button extends React.Component { //一个Button组件,返回一个根据颜色定义的按钮
render(){
return (
<button style={{'color' : this.props.color}}> //在button中使用props中的color
{this.props.children}
</button>
);
}
}
class Message extends React.Component { //在列表的子项目组件,其中包括Button组件
render(){
return (
<div>
{this.props.text}<Button color={this.props.color}>Delete</Button> //向Button传递color
</div>
);
}
}
class MessageList extends React.Component { //列表组件
render(){
const children = this.props.message.map((item, index) =>
<Message text={item} color={this.props.color} key={item}/> //向Message传递color
);
return (
<div>{children}</div>
);
}
}
class App extends React.Component { //app组件
constructor(props){
super(props);
this.state={
message: [
'hello1', 'hello2'
],
color: 'red'
};
}
render(){
return (
<div>
<MessageList message={this.state.message} color={this.state.color}/> //传递state中的color
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
从这个例子中,我们要聊的是color这个属性。你会发现color一直从App->MessageList->Message->Button,其中一共穿过了2个组件,当然了,这是我们有意为之。但是,我们传递的方式实在是丑陋。
那么,如果我们对之前的进行改写,使用context来直接跨组件传递,或许会好一点。将之前的代码进行修改
// Button部分修改
class Button extends React.Component {
render () {
return (
<button style={{color: this.context.color}}> //此处直接使用context
{this.props.children}
</button>
);
}
}
Button.contextTypes = { //增加Button组件中的contextTypes校验
color: PropTypes.String
};
//App组件部分修改
class App extends React.Component {
constructor(props){
super(props);
this.state = {
messages: [
'message1', 'message2'
],
color: 'red'
};
}
getChildContext(){ //增加一个返回context对象的函数getChildContext
return {color: 'red'};
}
render () {
return (
<div>
<MessageList messages={this.state.messages}/>
</div>
);
}
}
App.childContextTypes = { //这里增加childContextTypes校验
color: PropTypes.String
};
其实就是在App中增加了getChildContext()函数,以及childContextTypes和contextTypes两个context的类型校验——这里需要使用到prop-types。我们可以发现,经过这样子的处理,整个传递流程变成了App->Button,中间少了两层组件。整体看上去的风格简洁,有效。
context使用问题
看过上面的测试用例,我们会发现context其实使用起来挺方便的,那么,为何它只作为尝试性API,官方都不推荐使用呢?
因此,我对上述例子进行了一些改动,从这里我们可以看到context的最大的问题。
首先,我们在App组件中增加一个可以改变color值的按钮
class App extends React.Component { //修改后的App
constructor(props){
super(props);
this.state = {
messages: [
'message1', 'message2'
],
color: 'red'
};
this.changeColor = this.changeColor.bind(this);
}
getChildContext(){
return {color: this.state.color};
}
changeColor(){ //增加一个函数可以改变state中的color值
this.setState({
color: 'green'
});
}
render () {
return (
<div>
<MessageList messages={this.state.messages}/>
<button onClick={this.changeColor}>changeColor</button>
</div>
);
}
}
这样我们就可以完成将red转变成green的效果,但是,我们需要去Message组件中添加一个生命周期shouldComponentUpdate,让它的返回值为false,即判断它不更新。
class Message extends React.Component {
shouldComponentUpdate(){
return false;
}
render () {
return (
<div>
{this.props.text}<Button>Delete</Button>
</div>
);
}
}
这时,你再次点击按钮,你会发现无法改变button的颜色。
原因呢?主要是Message组件处阻断了组件的更新,导致的问题就是虽然context值被修改了,但是更深层次的组件却为更新,这或许是context最大的问题吧。而且context是一个全局的对象,或许存在会污染全局空间,这也是官方不建议使用它的原因。
总结
对于这种深层次的组件状态传递问题,现在的解决办法其实极为有限。
- 使用redux做一个全局的store管理
- 使用context进行一个直接传递(慎用)
- 模拟一个redux类似的行为,在全局空间中保存一个state。
- 使用props一层一层传递
个人觉得,一般组件超过3层以上的传递,就建议使用redux或者redux模拟。因为一般需要传递3层以上组件的,复杂度已经有点大了