React 組件性能優化


React組件性能優化

前言

眾所周知,瀏覽器的重繪和重排版(reflows & repaints)(DOM操作都會引起)才是導致網頁性能問題的關鍵。而React虛擬DOM的目的就是為了減少瀏覽器的重繪和重排版

說到React優化問題,就必須提下虛擬DOM虛擬DOM是React核心,通過高新的比較算法,實現了對界面上真正變化的部分進行實際的DOM操作(只是說在大部分場景下這種方式更加效率,而不是一定就是最效率的)。雖然虛擬DOM很牛逼(實際開發中我們根本無需關系其是如何運行的),但是也有缺點。如當React組件如下:

<Components> <Components-1 /> <Components-2 /> <Components-3 /> </Components>

數據變化從Components->Components-1傳遞下來,React不會只重渲染Components-1和其父組件,React會以變化(props和state的變化)的最上層的組件為准生成對比的虛擬DOM,就導致了組件沒必要的重渲染(即組件render方法的運行)。下面的3張圖是借用網上的,是對上面組件更新的圖表說明。

  • 更新綠色點組件(從根組件傳遞下來應用在綠色組件上的數據發生改變)

  • 理想狀態我們想只更新綠色點的組件

  • 實際圖中的組件都會重渲染(黃色的點是不必要的渲染,優化的方向)

React開發團隊也考慮到這個問題,為我們提供了一個組件函數處理數據量大的性能問題,shouldComponentUpdate,這個方法是我們的性能優化切入點。

虛擬DOM

虛擬DOM其實就是一個 JavaScript 對象。 React 使用虛擬DOM來渲染 UI,當組件狀態有更改的時候,React 會自動調用組件的 render 方法重新渲染整個組件的 UI。

當然如果真的這樣大面積的操作 DOM,性能會是一個很大的問題,所以 React 實現了一個虛擬 DOM,組件 DOM 結構就是映射到這個虛擬 DOM 上,React 在這個虛擬 DOM 上實現了一個 diff 算法,當要更新組件的時候,會通過 diff 尋找到要變更的 DOM 節點,再把這個修改更新到瀏覽器實際的 DOM 節點上,所以實際上不是真的渲染整個 DOM 樹。這個虛擬 DOM 是一個純粹的 JS 數據結構,所以性能會比原生 DOM 快很多。

組件渲染方式

組件渲染方式有兩種初始渲染更新渲染,而我們需要優化的地方就是更新渲染。

優化關鍵shouldComponentUpdate

組件更新生命周期中必調用shouldComponentUpdate,字面意思是組件是否應該更新。shouldComponentUpdate默認返回true,必更新。所有當我們判斷出組件沒必要更新是,shouldComponentUpdate可以返回false,就達到優化效果。那如何編寫判斷代碼呢?看下以下幾種方式。

官方PureRenderMixin

React 官方提供了 PureRenderMixin 插件,其使用方法如下:

官方說明

//官方例子 import PureRenderMixin from 'react-addons-pure-render-mixin'; class FooComponent extends React.Component { constructor(props) { super(props); this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); } render() { return <div className={this.props.className}>foo</div>; } }

在 React 的最新版本里面,提供了 React.PureComponent 的基礎類,而不需要使用這個插件。

這個插件其實就是重寫了 shouldComponentUpdate 方法,但是這都是最上層對象淺顯的比較,沒有進行對象深度比較,場景有所限制。那就需要我們自己重寫新的PureRenderMixin。

自定義PureRenderMixin

以下重寫方式是采用ES6,和React高階組件寫法,使用了lodash進行深度比較。可以看我在CodePen的例子React組件優化之lodash深度對比

import _ from 'lodash'; function shallowEqual(objA, objB) { if (objA === objB) { return true; } if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { return false; } const keysA = Object.keys(objA); const keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } const bHasOwnProperty = hasOwnProperty.bind(objB); for (let i = 0; i < keysA.length; i++) { const keyA = keysA[i]; if (objA[keyA] === objB[keyA]) { continue; } // special diff with Array or Object if (_.isArray(objA[keyA])) { if (!_.isArray(objB[keyA]) || objA[keyA].length !== objB[keyA].length) { return false; } else if (!_.isEqual(objA[keyA], objB[keyA])) { return false; } } else if (_.isPlainObject(objA[keyA])) { if (!_.isPlainObject(objB[keyA]) || !_.isEqual(objA[keyA], objB[keyA])) { return false; } } else if (!bHasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) { return false; } } return true; } function shallowCompare(instance, nextProps, nextState) { return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState); } function shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this, nextProps, nextState); } /* eslint-disable no-param-reassign */ function pureRenderDecorator(component) { //覆蓋了component中的shouldComponentUpdate方法 component.prototype.shouldComponentUpdate = shouldComponentUpdate; return component;//Decorator不用返回,直接使用高階組件需要return } /***** *使用ES6 class 語法糖如下,decorator的沒試過,decorator請使用上面的,不要return *let pureRenderDecorator = component => class { * constructor(props) { * super(props); * component.prototype.shouldComponentUpdate = shouldComponentUpdate; * } * render(){ * var Component = component;//自定義組件使用時要大寫 * return ( * <Component {...this.props}/> * ) * } *} ******/ export { shallowEqual }; export default pureRenderDecorator;

這種方式可以確保props和state數無變化的情況下,不重新渲染組件。但是進行了對象深度比較,是比較不划算的。這點Facebook也是有考慮的,所以就有了immutable-js

immutable-js

immutable-js這里就不詳說,這里貼一下React組件優化代碼,重寫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 (thisProps[key] !== nextProps[key] || !is(thisProps[key], nextProps[key])) { //console.debug(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; } ...//省略代碼

這里面的處理前提是要使用immutable-js對象,上面的代碼不是100%適合所有場景(如果全部的props和states都是immutable對象,這個是沒問題的),當props中有函數對象(原生的)時,這個就會失效,需要做些而外處理。

對於 Mutable 的對象(原生的js對象就是Mutable的)的低效率操作主要體現在 復制 和 比較 上,而 Immutable 對象就是解決了這兩大低效的痛點。

immutable-js的比較是比lodash深度對象比較是更有效率的。

總結

immutable-js的思想其實是跟React的虛擬DOM是一致的,都是為了減少不必要的消耗,提高性能。虛擬DOM內部處理比較復雜,而且可能還會帶有一些開發人員的副作用(render中運行了一些耗時的程序),算法比較完后會相對耗時。而 immutable-jslodash只是純凈的比較數據,效率是相對比較高的,是目前比較適合使用的PureRender方式。建議采用immutable-js,也可以根據項目性質決定。(ps:持續更新歡迎指正)

參考文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM