laizimo/zimo-article

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是一个全局的对象,或许存在会污染全局空间,这也是官方不建议使用它的原因。

总结

对于这种深层次的组件状态传递问题,现在的解决办法其实极为有限。

  1. 使用redux做一个全局的store管理
  2. 使用context进行一个直接传递(慎用)
  3. 模拟一个redux类似的行为,在全局空间中保存一个state。
  4. 使用props一层一层传递

个人觉得,一般组件超过3层以上的传递,就建议使用redux或者redux模拟。因为一般需要传递3层以上组件的,复杂度已经有点大了