1. 程式人生 > >react效能優化

react效能優化

寫在前面的話:

要想解決問題,首先得找到問題的根源,所以,說起效能分析,還是要從其生命週期和渲染機制說起。

1.渲染機制

react的元件渲染分為初始化渲染和更新渲染,在初始化渲染的時候會呼叫根元件下的所有元件的render方法進行渲染。

但是當我們要更新某個子元件的時候,是從根元件傳遞下來應用在子元件上的資料發生改變。
我們只是希望呼叫關鍵路徑上元件的render就好了。
但是,
react的預設做法是呼叫所有元件render,再對生成的虛擬DOM進行對比,如不變就不進行更新。這樣的render和虛擬DOM的對比明顯實在浪費時間。
注意:

拆分元件是有利於複用和元件優化的,其次,生成虛擬DOM並進行對比發生在render之後,而不是render之前。

總得來說,父親元件的props和state發生變化時,他和他的子子孫孫元件等後代都要重新渲染。

2.更新階段的生命週期

這裡寫圖片描述
1.componementWillReceiveProp:當掛載的元件接受到新的props時會被呼叫,此方法應該被用於比較this.props和nextProps以用於使用this.setState()執行狀態轉換,(元件內部資料有變化,使用state,但是在更新階段又要在props改變的時候改變state,則在這個生命週期裡面)

2.shouldComponentUpdate:當元件決定任何改變是否要更新到DOM時被呼叫,作為一個優化實現比較this.props和nextProps,this.state和nextState,如果應該跳過更新,返回false。

3.componentWillUpdate:在更新發生前被立即呼叫,此時不能呼叫this.setState()

4.componentDidUpdate:在更新發生後被立即呼叫。(可以在DOM更新完之後,做一些收尾工作)

react的優化是基於shouldComponentUpdate的,該生命週期預設返回true,所以一旦prop或state有任何變化,都會引起重新render。

所以,可以得出react的效能優化就是圍繞shouldComponentUpdate(SCU)方法來進行的,其優化無外乎兩點:

  • 縮短SCU方法的執行時間(或者直接不執行)
  • 沒必要的渲染,SCU就該返回false

shouldComponentUpdate:

react在每個元件生命週期更新的時候都會呼叫一個shouldComponentUpdate(nextprops,nextState)函式,他的職責就是返回true,或者false,true表示需要更新,false就是表示不需要,預設值是返回true的,即便你沒有顯示的定義shouldComponentUpdate函式,他的返回值還是true。

根據渲染流程,首先會判斷shouldcomponentUpdate是否需要更新,如果需要更新,則呼叫元件的render生成新的虛擬DOM,然後再與舊的虛擬DOM進行對比,如果對比一致就不更新,如果對比不同,則根據最小粒度改變去更新DOM,如果scu不需要更新,則直接保持不變,同時其子元素也保持不變。

注意:錯誤寫法

  • {…this.props}(不能濫用,只傳遞component需要的props即可,傳遞的太多,或者層次傳的太深,都會加重shuoldComponentUpdate,其裡面的資料比較負擔,因此一定要慎重使用spread attributes(<Component {...props}/>))
  • ::this.handleChange()(將該方法的bind一律置於constructor)
  • 複雜的頁面不要在一個元件中寫完
  • 儘量使用const element定義
  • map裡面新增key,並且key不要使用index(可變的)
  • 儘量少的使用setTimeout或者不可控的refs,DOM操作
  • props和state的資料儘可能簡單明瞭,扁平化。
  • 使用return null而不是css的display:none來控制節點的隱藏。保證同一時間頁面的DOM節點儘可能的少。

3.效能分析工具

React特色工具:Perf

react官方提供一個外掛react.addons.perf可以幫助我們分析元件的效能,以確認是否需要優化。

Perf 是react官方提供的效能分析工具。Perf最核心的方法莫過於Perf.printWasted(measurements),該方法會列出那些沒必要的元件渲染。很大程度上,React的效能優化就是幹掉這些無謂的渲染。

操作:
開啟console面板,先輸入Perf.start()執行一些元件操作,引起資料變動,元件更新,然後輸入Perf.stop()。(建議一次只執行一個操作,好進行分析)
然後在輸入Perf.printInclusive將所有涉及到的元件render列印下來。(官方圖片)
這裡寫圖片描述
或者輸入Perf.printWasted()檢視不需要的浪費元件的render。
這裡寫圖片描述

優化前:
這裡寫圖片描述
優化後:
這裡寫圖片描述

4.其它檢測工具

react-perf-tool為react提供了一種視覺化的效能檢測方案,其同樣是基於React.addons,但是使用圖示來顯示結果更加方便。

1.PureRenderMixin(基於es5)

var PureRenderMixin = require('react-addons-pure-render-mixin');
React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return <div className={this.props.className}>foo</div>;
  }
});

2.Shallow Compare(基於es6)

var shallowCompare = require('react-addons-shallow-compare');
export class SampleComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

  render() {
    return <div className={this.props.className}>foo</div>;
  }
}

pureRender很簡單,就是把穿進來的component的shouldComponentUpdate給重寫掉,原來的shouldComponentUpdate,無論怎樣都是return true,現在不會這樣了,我要用shallowCompare比一比,shallowCompare程式碼及其簡單,如下:

function shallowCompare(instance, nextProps, nextState) {
  return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState);
}

但是這樣做還是有缺點的:
shallowEqual其實只比較props的第一層,子屬性是不是相同,如果props是如下:

{
  detail: {
    name: "123",
    age: "123"
  }
}

他只會比較props.detail===nextProps.detail,這就會導致在傳入複雜的資料的情況下,優化會失效。

7.補充

react在15.3.0裡面加入了react.PureComponent一個可繼承的新的基礎類,用來替換react-addons-pure-render-mixin,用法如下:

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>
    );
  }
}

8.immutable.js

我們也可以在shouldComponentUpdate()中使用deepCopy和deepCompare來避免無必要的render(),但是deepCopy和deepCompare一般都是非常耗效能的。

Immutable Date就是一旦建立,就不能再被更改的資料。對Immutable物件的任何修改或者新增刪除操作都會返回一個新的Immutable物件。

Immutable實現的原理是Persistent Data Structure(持久化資料結構),也就是使用舊資料建立新資料時,要保證舊資料同時可用且不變,同時為了避免deeepCopy把所有的節點都複製一遍帶來的效能損耗,Immutable使用了,Structural Sharing(結構共享)即如果物件樹中一個節點發生變化,只修改這個節點和受他影響的父節點,其他節點則進行共享。

Immutable則提供了簡潔高效的判斷資料是否變化的方法,只需要===和is比較就能知道是否需要執行render(),而這個操作幾乎0成本,所以可以極大提高效能,修改後的shouldComponentUpdate是這樣的:

import { is } from 'immutable';

shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
  const thisProps = this.props || {}, thisState = this.state || {};

  if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
      Object.keys(thisState).length !== Object.keys(nextState).length) {
    return true;
  }

  for (const key in nextProps) {
    if (!is(thisProps[key], nextProps[key])) {
      return true;
    }
  }

  for (const key in nextState) {
    if (thisState[key] !== nextState[key] || !is(thisState[key], nextState[key])) {
      return true;
    }
  }
  return false;
}