1. 程式人生 > >React效能優化 PureComponent 使用指南

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 不能使用同一個引用哦。