Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react context解析 #25

Open
laizimo opened this issue Sep 7, 2017 · 1 comment
Open

react context解析 #25

laizimo opened this issue Sep 7, 2017 · 1 comment

Comments

@laizimo
Copy link
Owner

laizimo commented Sep 7, 2017

前言

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层以上组件的,复杂度已经有点大了

@laizimo
Copy link
Owner Author

laizimo commented Sep 8, 2017

Context-React

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant