React效能優化方案之PureComponent

vi_young發表於2019-03-04

PureComponent介紹

之前的一篇文章React效能優化方案之PureRenderMixin,是對react的shouldComponentUpdate的方法進行重寫去優化。但自從React15.3中新加了一個 PureComponent 類,易於在自己的元件使用,只需要將Component 換成 PureComponent 即可,所以原作者建議使用今天要介紹的PureComponent。

PureComponent原理

我們知道元件的state和props的改變就會觸發render,但是元件的state和props沒發生改變,render就不執行,只有PureComponent檢測到state或者props發生變化時,PureComponent才會呼叫render方法,因此,你不用手動寫額外的檢查,就可以達到提高效能的而目的。實際上react做了最外層的淺比較:

if (this._compositeType === CompositeTypes.PureClass) {
  shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);
}
複製程式碼

shadowEqual只會淺檢查元件的props和state,所以巢狀物件和陣列是不會被比較的。深比較是要逐層遍歷列舉對應的鍵值進行比對,這個操作比較浪費時間。如果比較的深的情況,你也可以使用shouldComponentUpdate來手動單個比較是否需要重新渲染。最簡單的方式就是直接比較props或state:

shouldComponentUpdate(nextProps, nextState) {
  return nextProps.xxx.xx === props.xxx.xx;
}
複製程式碼

另外,你可以使用immutable屬性,Immutable.js就是一個Immutable庫。這種情況下,屬性的比較是非常容易的,因為已存在的物件就不會發生改變,取而代之的是重新建立新的物件。

使用PureComponent

爬坑1:易變資料不能使用一個引用

PureComponent節約了我們的時間,避免了多餘的程式碼。那麼,掌握如何正確使用它是非常重要的,否則如果使用不當,它就無法發揮作用。例如,讓我們想想這樣一種情況,父元件有一個render方法和一個click處理方法:

handleClick() {
  let {items} = this.state

  items.push(`new-item`)
  this.setState({ items })
}

render() {
  return (
    <div>
      <button onClick={this.handleClick} />
      <ItemList items={this.state.items} />
    </div>
  )
}
複製程式碼

由於ItemList是一個純元件,這時候點選它是不會被渲染的,但是我們的確向this.state.items加入了新的值,但是它仍然指向同一個物件的引用。但是,通過移除可變物件就很容易改變這種情況,使之能夠正確被渲染。

handleClick() {
  this.setState(prevState => ({
    words: prevState.items.concat([`new-item`])
  }))
}
複製程式碼

爬坑2:不變資料使用一個引用

上面易變資料不能使用一個引用的案例中有一個點選刪除操作,如果我們刪除的程式碼這麼寫:

constructor(props){
    super(props)
    this.state={
        items: [{a: 1}, {a: 2}, {a: 3}]
    }
}
handleClick = () => {
  const { items } = this.state;
  items.splice(items.length - 1, 1);
  this.setState({ items });
}
複製程式碼

items 的引用也是改變的,但如果 items 裡面是引用型別資料這時候state.items[0] === nextState.items[0]是false,子元件裡還是重新渲染了。這樣就需要我們保證不變的子元件資料的引用不能改變。這個時候可以使用前面說的immutable-js函式庫。

爬坑3:父給子通過props傳遞迴調函式

我們在子父通訊傳遞迴調函式時候:

// step1
<MyInput onChange={e => this.props.update(e.target.value)} />
// step2
update(e) {
  this.props.update(e.target.value)
}
render() {
  return <MyInput onChange={this.update.bind(this)} />
}

複製程式碼

由於每次 render 操作 MyInput 元件的 onChange 屬性都會返回一個新的函式,由於引用不一樣,所以父元件的 render 也會導致 MyInput 元件的 render
,即使沒有任何改動,所以需要儘量避免這樣的寫法,最好封裝:

update = (e) => {
  this.props.update(e.target.value)
}
render() {
  return <MyInput onChange={this.update} />
}
複製程式碼

爬坑4:新陣列,空陣列,也會導致元件重新渲染

<Entity values={this.props.values || []}/>
複製程式碼

為了避免這個問題,你可以使用defaultProps,它包含了一個屬性的初始化空狀態。在純元件(PureComponent)被建立時,因為函式的新物件被建立了,所以它會獲得新資料,並且重新渲染。解決這個問題最簡單的方法就是: 在元件的constructor方法中使用bind。

constructor(props) {
    super(props)
    this.update = this.update.bind(this)
}
update(e) {
    this.props.update(e.target.value)
}
render() {
    return <MyInput onChange={this.update} />
}
複製程式碼

同時,在JSX中,任何包含子元素(child elements)的元件,shallowEqual檢查總會返回false。

PureComponent使用方式

用法很簡單:

import React { PureComponent, Component } from `react`;

class Foo extends (PureComponent || Component) {
  //...
}
複製程式碼

老版本相容寫法,在老版本的 React 裡也不會報錯的。

總結

1.純元件忽略重新渲染時,不僅會影響它本身,而且會影響它的說有子元素,所以,使用PureComponent的最佳情況就是展示元件,它既沒有子元件,也沒有依賴應用的全域性狀態

2.PureComponent 真正起作用的,只是在一些純展示元件上,複雜元件用了也沒關係,反正 shallowEqual 那一關就過不了,不過記得 props 和 state 不能使用同一個引用。

相關文章