我们知道,当组件的props或者state发生变化的时候:React会对组件当前的Props和State分别与nextProps和nextState进行比较,当发现变化时,就会对当前组件以及子组件
进行重新渲染,否则就不渲染。有时候为了避免组件进行不必要的重新渲染,我们通过定义shouldComponentUpdate
来优化性能。例如如下代码:
class CounterButton extends React.Component { constructor(props) { super(props); this.state = {count: 1}; } shouldComponentUpdate(nextProps, nextState) { if (this.props.color !== nextProps.color) { return true; } if (this.state.count !== nextState.count) { return true; } return false; } render() { return ( <button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button> ); } }
shouldComponentUpdate
通过判断props.color
和state.count
是否发生变化来决定需不需要重新渲染组件,当然有时候这种简单的判断,显得有些多余和样板化,于是React就提供了PureComponent
来自动帮我们做这件事,这样就不需要手动来写shouldComponentUpdate
了:
class CounterButton extends React.PureComponent { constructor(props) { super(props); this.state = {count: 1}; } render() { return ( <button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button> ); } }
大多数情况下, 我们使用PureComponent
能够简化我们的代码,并且提高性能,但是PureComponent
的自动为我们添加的shouldComponentUpate
函数,只是对props和state进行浅比较(shadow comparison),当props或者state本身是嵌套对象或数组等时,浅比较并不能得到预期的结果,这会导致实际的props和state发生了变化,但组件却没有更新的问题,例如下面代码有一个ListOfWords
组件来将单词数组拼接成逗号分隔的句子,它有一个父组件WordAdder
让你点击按钮为单词数组添加单词,但他并不能正常工作:
class ListOfWords extends React.PureComponent { render() { return <div>{this.props.words.join(',')}</div>; } } class WordAdder extends React.Component { constructor(props) { super(props); this.state = { words: ['marklar'] }; this.handleClick = this.handleClick.bind(this); } handleClick() { // 这个地方导致了bug const words = this.state.words; words.push('marklar'); this.setState({words: words}); } render() { return ( <div> <button onClick={this.handleClick} /> <ListOfWords words={this.state.words} /> </div> ); } }
这种情况下,PureComponent
只会对this.props.words进行一次浅比较,虽然数组里面新增了元素,但是this.props.words与nextProps.words指向的仍是同一个数组,因此this.props.words !== nextProps.words 返回的便是flase,从而导致ListOfWords组件没有重新渲染,笔者之前就因为对此不太了解,而随意使用PureComponent,导致state发生变化,而视图就是不更新,调了好久找不到原因~。
最简单避免上述情况的方式,就是避免使用可变对象作为props和state,取而代之的是每次返回一个全新的对象,如下通过concat
来返回新的数组:
handleClick() { this.setState(prevState => ({ words: prevState.words.concat(['marklar']) })); }
你可以考虑使用Immutable.js
来创建不可变对象,通过它来简化对象比较,提高性能。
这里还要提到的一点是虽然这里虽然使用了Pure
这个词,但是PureComponent
并不是纯的,因为对于纯的函数或组件应该是没有内部状态,对于stateless component
更符合纯的定义,不了解纯函数的同学,可以参见这篇文章。