回到頂部

在react項目中使用shouldComponentUpdate方法進行組件性能優化

時間:2年前   作者:龐順龍   瀏覽:3820   [站內原創,轉載請注明出處]

標簽: Node.js  

在react項目中使用shouldComponentUpdate方法進行組件性能優化,前半部分主要是思路和官方方法解釋,最后兩張大圖為測試對比結果。

react的渲染流程主要是

初始化組件

該階段會執行組件及其所有子組件的render方法,從而生成第一版的虛擬dom。

組件更新渲染

組件的props或者state任意發生改變就會觸發組件的更新渲染。默認情況下其也會執行該組件及其所有子組件的render方法獲取新的虛擬dom。

接下來我們聊聊組件更新渲染的性能問題。

react組件更新流程

通過上面分析可以知道組件更新具體過程如下:

執行該組件及其所有子組件的render方法獲取更新后的虛擬DOM,即re-render,即使子組件無需更新。
然后對新舊兩份虛擬DOM進行diff來進行組件的更新
在這個過程中,可以通過組件的shouldComponentUpdate方法返回值來決定是否需要re-render。

react的整個更新渲染流程可以借用一張圖來加以說明:


默認地,組件的shouldComponentUpdate返回true,即React默認會調用所有組件的render方法來生成新的虛擬DOM, 然后跟舊的虛擬DOM比較來決定組件最終是否需要更新。

性能瓶頸分析

借圖說話,例如下圖是一個組件結構tree,當我們要更新某個子組件的時候,如下圖的綠色組件(從根組件傳遞下來應用在綠色組件上的數據發生改變):

理想情況下,我們只希望關鍵路徑上的組件進行更新,如下圖:


但是,實際效果卻是每個組件都完成re-render和virtual-DOM diff過程,雖然組件沒有變更,這明顯是一種浪費。如下圖黃色部分表示浪費的re-render和virtual-DOM diff。


根據上面的分析,react的性能瓶頸主要表現在:
對于props和state沒有變化的組件,react也要重新生成虛擬DOM及虛擬DOM的diff。
這個時候,就是shouldComponentUpdate上場的時候了。

shouldComponentUpdate(nextProps, nextState){
   return !isEqual(nextProps, this.props) || !isEqual(nextState, this.state)
}
其中,isEqual方法為判斷兩個對象是否相等(指的是其對象內容相等,而不是全等)。

通過顯示覆蓋shouldComponentUpdate方法來判斷組件是否需要更新從而避免無用的更新,但是若為每個組件添加該方法會顯得繁瑣,好在react提供了官方的解決方案,具體做法:

方案對組件的shouldComponentUpdate進行了封裝處理,實現對組件的當前屬性和狀態與上一次的進行淺對比,從而決定組件是否需要更新。

react在發展的不同階段提供兩套官方方案:

PureRenderMin


一種是基于ES5的React.createClass創建的組件,配合該形式下的mixins方式來組合PureRenderMixin提供的shouldComponentUpdate方法。當然用ES6創建的組件也能使用該方案。


import PureRenderMixin from 'react-addons-pure-render-mixin';
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
PureComponent


該方案是在React 15.3.0版本發布的針對ES6而增加的一個組件基類:React.PureComponent。這明顯對ES6方式創建的組件更加友好。


import React, { PureComponent } from 'react'
class Example extends PureComponent {
  render() {
    // ...
  }
}
需要指出的是,不管是PureRenderMin還是PureComponent,他們內部的shouldComponentUpdate方法都是淺比較(shallowCompare)props和state對象的,即只比較對象的第一層的屬性及其值是不是相同。例如下面state對象變更為如下值:



state = {
  value: { foo: 'bar' }
}
因為state的value被賦予另一個對象,使nextState.value與this.props.value始終不等,導致淺比較通過不了。在實際項目中,這種嵌套的對象結果是很常見的,如果使用PureRenderMin或者PureComponent方式時起不到應有的效果。
雖然可以通過深比較方式來判斷,但是深比較類似于深拷貝,遞歸操作,性能開銷比較大。
為此,可以對組件盡可能的拆分,使組件的props和state對象數據達到扁平化,結合著使用PureRenderMin或者PureComponent來判斷組件是否更新,可以更好地提升react的性能,不需要開發人員過多關心。

案例分析

TimeLineView 列表頁組件面
TimeLineEventView 列表item組件頁面
說明:列表頁面 數據list.map(TimeLineEventView)

現有一個列表頁面,滾動加載下一頁,每個項目中均有一個點贊功能,按照邏輯我們一般是在列表jsx頁面通過數據list.map(頁面item方法)來實現,通過頁面state來處理點贊按鈕的樣式切換:

我們不對列表item進行shouldComponentUpdate處理,下面通過Perf工具來檢測當我們點擊某一個item贊功能的時候,通過Perf輸出頁面性能檢測結果:


如上圖的Print Wasted 模塊,我們可以看到當前列表頁面,我們點擊某一個item的贊功能,頁面會產生將近20ms的性能損耗,主要就是item組件 TimeLineEventView 被重新渲染了17次,而這17次都是沒有任何變化的item產生的。
然后我們改造TimeLineEventView頁面,增加shouldComponentUpdate處理:


shouldComponentUpdate(nextProps, nextState) {
    if (this.props.IsPraised == nextProps.IsPraised) {
      return false;
    }
    return true;
  }
上面的代碼就是充寫了react默認返回true的shouldComponentUpdate方法,通過邏輯可知,只有被點擊的item組件才會返回true,其他均為false,這樣就會實現非相關組件不會重新render渲染,改造完畢后,我們再次通過Perf工具進行檢測測試,結果如下:


繼續看上圖的Print Wasted 模塊,可以看到,已經沒有 TimeLineEventView 相關的組件渲染損耗記錄了,說明我們優化成功,目前的渲染損耗在1ms左右,雖然從 20ms 到 1ms并不會對用戶產生很大的感知,但是對于滾動加載列表來說,如果數據結構復雜,頁面數據量達到幾百條,那么損耗的性能可能就是7.8百ms,對于頁面來說就會很明顯的卡頓等,所以,對組件進行足夠、合理的扁平化拆分就顯得尤為必要了。

參考:
http://www.cnblogs.com/wonyun/p/6804952.html
http://benchling.engineering/deep-dive-react-perf-debugging/
https://segmentfault.com/a/1190000006741060

React Perf chrome插件地址:
https://chrome.google.com/webstore/detail/react-perf/hacmcodfllhbnekmghgdlplbdnahmhmm


內容均為作者獨立觀點,不代表八零IT人立場,如涉及侵權,請及時告知。

評論努力加載中...
暫無評論
暫無評論

手機掃碼閱讀

熱門相關

加載中...
關于我們   聯系我們   申請友鏈   贊助記錄   站點地圖
? 2014 - 2017 www.1043959.live All Rights Reserved. 京ICP備14042174號-1
本站遵循 CC BY 4.0 協議,轉載請注明出處 。
股票配资平台正规