React 組件性能優化

React組件性能優化

前言

衆所周知,瀏覽器的重繪和重排版(reflows & repaints)(DOM操做都會引發)纔是致使網頁性能問題的關鍵。而React虛擬DOM的目的就是爲了減小瀏覽器的重繪和重排版javascript

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

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

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

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

react 組件渲染 更新子組件

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

react 組件渲染 理想渲染

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

react 組件渲染 實際渲染

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

虛擬DOM

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

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

組件渲染方式

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

優化關鍵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:持續更新歡迎指正)

參考文章

相關文章
相關標籤/搜索