默认渲染行为的问题
在React Component的生命周期中,有一个shouldComponentUpdate方法。这个方法默认返回值是true。
这意味着就算没有改变组件的props或者state,也会导致组件的重绘。这就经常导致组件因为不相关数据的改变导致重绘,这极大的降低了React的渲染效率
调用了setState方法后, 会触发组件的render方法, 并且state如果有传给子组件的话也会导致子组件的更新, 但是如果 setState前后组件的state并没有改变的话, 还是会触发组件极其子组件的更新, 这样就造成了性能的浪费, 所以可以使用react提供的 PureComponent组件.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import React, { Component, PureComponent } from 'react';
class Temp extends PureComponent { render() { console.log('render Temp'); return ( <div>{ this.props.val }</div> ); } }
class App extends Component { state = { val: 1 }
componentDidMount() { setInterval(() => { this.setState({ val: 1 }) }, 2000) }
render() { console.log('render App'); return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Welcome to React</h1> </header> <p className="App-intro"> To get started, edit <code>src/App.js</code> and save to reload. </p> <Temp val={ this.state.val } /> </div> ); } }
export default App;
|
如果Temp组件不使用PureComponent组件, 而是使用普通的无状态组件的话, 当父组件的定时器在跑的时候, Temp组件也会不断的render, 造成了性能的浪费. 使用PureComponent后可以看到, Temp组件不会render了
好处: 提升性能, 可以少写shouldComponentUpdate
原理
当组件更新时,如果组件的 props 和 state 都没发生改变, render 方法就不会触发,省去 Virtual DOM 的生成和比对过程,达到提升性能的目的。具体就是 React 自动帮我们做了一层浅比较:
1 2 3
| if (this._compositeType === CompositeTypes.PureClass) { shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState); }
|
而 shallowEqual 又做了什么呢?会比较 Object.keys(state | props) 的长度是否一致,每一个 key 是否两者都有,并且是否是一个引用,也就是只比较了第一层的值,确实很浅,所以深层的嵌套数据是对比不出来的。
易变数据不能使用一个引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class App extends PureComponent { state = { items: [1, 2, 3] } handleClick = () => { const { items } = this.state; items.pop(); this.setState({ items }); } render() { return (<div> <ul> {this.state.items.map(i => <li key={i}>{i}</li>)} </ul> <button onClick={this.handleClick}>delete</button> </div>) } }
|
会发现,无论怎么点 delete 按钮, li 都不会变少,因为 items 用的是一个引用, shallowEqual 的结果为 true 。改正
1 2 3 4 5
| handleClick = () => { const { items } = this.state; items.pop(); this.setState({ items: [].concat(items) }); }
|
这样每次改变都会产生一个新的数组,也就可以 render 了。
结束语
PureComponent 真正起作用的,只是在一些纯展示组件上,复杂组件用了也没关系,反正 shallowEqual 那一关就过不了,不过记得 props 和 state 不能使用同一个引用哦。