React效能優化 PureComponent 使用指南
為什麼使用?
轉載https://wulv.site/2017-05-31/react-purecomponent.html
React15.3中新加了一個 PureComponent
類,顧名思義, pure
是純的意思, PureComponent
也就是純元件,取代其前身 PureRenderMixin
, PureComponent
是優化 React
應用程式最重要的方法之一,易於實施,只要把繼承類從 Component
換成 PureComponent
render
操作的次數,從而提高效能,而且可以少寫 shouldComponentUpdate
函式,節省了點程式碼。
原理
當元件更新時,如果元件的 props
和 state
都沒發生改變, render
方法就不會觸發,省去 Virtual DOM
的生成和比對過程,達到提升效能的目的。具體就是 React
自動幫我們做了一層淺比較:
1 2 3 4 |
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
了。這裡有一個矛盾的地方,如果沒有 items.pop();
操作,每次 items
資料並沒有變,但還是 render
了,這不就很操蛋麼?呵呵,資料都不變,你 setState
幹嘛?
不變資料使用一個引用
子元件資料
上面易變資料不能使用一個引用的案例中有一個點選刪除操作,如果我們刪除的程式碼這麼寫:
1 2 3 4 5 |
handleClick = () => { const { items } = this.state; items.splice(items.length - 1, 1); this.setState({ items }); } |
items
的引用也是改變的,但如果 items
裡面是引用型別資料:
1 |
items: [{a: 1}, {a: 2}, {a: 3}] |
這個時候
1 |
state.items[0] === nextState.items[0] // false |
子元件裡還是re-render
了。這樣就需要我們保證不變的子元件資料的引用不能改變。這個時候可以使用immutable-js函式庫。
函式屬性
我們在給元件傳一個函式的時候,有時候總喜歡:
1 2 3 4 5 6 7 8 9 |
// 1 <MyInput onChange={e => this.props.update(e.target.value)} /> // 2 update(e) { this.props.update(e.target.value) } render() { return <MyInput onChange={this.update.bind(this)} /> } |
由於每次 render
操作 MyInput
元件的 onChange
屬性都會返回一個新的函式,由於引用不一樣,所以父元件的 render
也會導致 MyInput
元件的 render
,即使沒有任何改動,所以需要儘量避免這樣的寫法,最好這樣寫:
1 2 3 4 5 6 7 |
// 1,2 update = (e) => { this.props.update(e.target.value) } render() { return <MyInput onChange={this.update} /> } |
空物件、空陣列或固定物件
有時候後臺返回的資料中,陣列長度為0或者物件沒有屬性會直接給一個 null
,這時候我們需要做一些容錯:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class App extends PureComponent { state = { items: [{ name: 'test1' }, null, { name: 'test3' }] } store = (id, value) => { const { items } = this.state; items[id] = assign({}, items[id], { name: value }); this.setState({ items: [].concat(items) }); } render() { return (<div> <ul> {this.state.items.map((i, k) => <Item style={{ color: 'red' }} store={this.store} key={k} id={k} data={i || {}} />) } </ul> </div>) } } |
當某一個子元件呼叫 store
函式改變了自己的那條屬性,觸發 render
操作,如果資料是 null
的話 data
屬性每次都是一個 {}
,{} ==== {}
是 false
的,這樣無端的讓這幾個子元件重新 render
了。{ color: 'red' }
也是一樣。
最好設定一個 defaultValue
為 {}
,如下:
1 2 3 |
static defaultValue = {} const style = { color: 'red' }; <Item style={style} store={this.store} key={k} id={k} data={i || defaultValue} /> |
複雜狀態與簡單狀態不要共用一個元件
這點可能和 PureComponent
沒多少關係,但做的不好可能會浪費很多效能,比如一個頁面上面一部分是一個複雜的列表,下面是一個輸入框,抽象程式碼:
1 2 3 4 5 6 7 8 9 10 11 |
change = (e) => { this.setState({ value: e.target.value }); } render() { return (<div> <ul> {this.state.items.map((i, k) => <li key={k}> {...}</li>)} </ul> <input value={this.state.value} onChange={this.change} /> </div>) } |
表單和列表其實是沒有什麼關聯的,表單的值也可能經常變動,但它的會給列表也帶來必然的 diff
操作,這是沒必要的,最好是給列表抽出成一個單獨的 PureComponent
元件,這樣 state.items
不變的話,列表就不會重新 render
了。
與 shouldComponentUpdate
共存
如果 PureComponent
裡有 shouldComponentUpdate
函式的話,直接使用 shouldComponentUpdate
的結果作為是否更新的依據,沒有 shouldComponentUpdate
函式的話,才會去判斷是不是 PureComponent
,是的話再去做 shallowEqual
淺比較。
1 2 3 4 5 6 7 8 9 10 11 |
// 這個變數用來控制組件是否需要更新 var shouldUpdate = true; // inst 是元件例項 if (inst.shouldComponentUpdate) { shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext); } else { if (this._compositeType === CompositeType.PureClass) { shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState); } } |
老版本相容寫法
1 2 3 4 5 |
import React { PureComponent, Component } from 'react'; class Foo extends (PureComponent || Component) { //... } |
這樣在老版本的 React
裡也不會掛掉。
總結
PureComponent
真正起作用的,只是在一些純展示元件上,複雜元件用了也沒關係,反正 shallowEqual
那一關就過不了,不過記得 props
和 state
不能使用同一個引用哦。