使用immutable優化React

文章同步於Github Pines-Cheng/blogjavascript

React在減小重複渲染方面確實是有一套獨特的處理辦法,那就是虛擬DOM,但顯然在首次渲染的時候React絕無可能超越原生的速度,或者必定能將其它的框架比下去。尤爲是在優化前的React,每次數據變更都會執行render,大大影響了性能,特別是在移動端。html

React 默認的渲染行爲

初始化渲染

在初始化渲染時,咱們須要渲染整個應用
(綠色 = 已渲染節點)
imagejava

提出改變

咱們想更新一部分數據。這些改變只和一個葉子節點相關(綠色的)react

image

理想更新

咱們只想渲染通向葉子節點的關鍵路徑上的這幾個節點(綠色的)
imagegit

默認行爲

若是你不告訴 React 別這樣作,它便會如此
(橘黃色 = 浪費的渲染)程序員

image

從上圖能夠看見,組件除了必要渲染的三個節點外,還渲染了其餘沒必要要渲染的節點,這對性能是一個很大的浪費。若是對於複雜的頁面,這將致使頁面的總體體驗效果很是差。所以要提升組件的性能,就應該想盡一切方法減小沒必要要的渲染。github

React的生命週期

React的生命週期以下,還沒熟悉的同窗能夠去熟悉一下。
imageweb

shouldComponentUpdate

由於其中的 shouldComponentUpdate 是優化的關鍵。React的重複渲染優化的核心其實就是在shouldComponentUpdate裏面作數據比較。在優化以前,shouldComponentUpdate是默認返回true的,這致使任什麼時候候觸發任何的數據變化都會使component從新渲染。這必然會致使資源的浪費和性能的低下——你可能會感受比較原生的響應更慢。segmentfault

React性能優化的關鍵在於shouldComponentUpdate
imageapi

在上面的示例中,由於 C2 的 shouldComponentUpdate 返回 false,React 就不須要生成新的虛擬 DOM,也就不須要更新 DOM,注意 React 甚至不須要調用 C4 和 C5 的 shouldComponentUpdate

C1 和 C3 的 shouldComponentUpdate 返回 true,因此 React 須要向下到葉子節點檢查它們,C6 返回 true,由於虛擬 DOM 不相等,須要更新 DOM。最後感興趣的是 C8,對於這個節點,React 須要計算虛擬 DOM,可是由於它和舊的相等,因此不須要更新 DOM。

React.PureComponent

在傳入組件的props和state只有一層時,咱們能夠直接使用 React.PureComponent,它會自動幫咱們進行淺比較(shallow-compare),從而控制shouldComponentUpdate的返回值。

可是,當傳入props或state不止一層,或者未array和object時,淺比較(shallow-compare)就失效了。固然咱們也能夠在 shouldComponentUpdate() 中使用使用 deepCopydeepCompare 來避免無必要的 render(),但 deepCopydeepCompare 通常都是很是耗性能的。這個時候咱們就須要 Immutable

Immutable

JavaScript 中的對象通常是可變的(Mutable),由於使用了引用賦值,新的對象簡單的引用了原始對象,改變新的對象將影響到原始對象。如

foo={a: 1}; 
bar=foo; 
bar.a=2

你會發現此時 foo.a 也被改爲了 2。雖然這樣作能夠節約內存,但當應用複雜後,這就形成了很是大的隱患,Mutable 帶來的優勢變得得不償失。爲了解決這個問題,通常的作法是使用 shallowCopy(淺拷貝)或 deepCopy(深拷貝)來避免被修改,但這樣作形成了 CPU 和內存的浪費。

而Immutable 能夠很好地解決這些問題。

什麼是Immutable Data

Immutable Data 就是一旦建立,就不能再被更改的數據。對 Immutable 對象的任何修改或添加刪除操做都會返回一個新的 Immutable 對象。Immutable 實現的原理是 Persistent Data Structure(持久化數據結構),也就是使用舊數據建立新數據時,要保證舊數據同時可用且不變。同時爲了不 deepCopy 把全部節點都複製一遍帶來的性能損耗,Immutable 使用了 Structural Sharing(結構共享),即若是對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享。

能夠看看下面這個經典的動畫:

image

immutable.js

Immutable.js本質上是一個JavaScript的持久化數據結構的庫 ,可是因爲同期的React太火,而且和React在性能優化方面完美無缺的配合,致使你們經常把它們二者綁定在一塊兒。

Immutable.js是Facebook 工程師 Lee Byron 花費 3 年時間打造,但沒有被默認放到 React 工具集裏(React 提供了簡化的 Helper)。它內部實現了一套完整的 Persistent Data Structure,且數據結構和方法很是豐富(徹底不像JS出身的好很差)。像 Collection、List、Map、Set、Record、Seq。有很是全面的map、filter、groupBy、reduce、find函數式操做方法。同時 API 也儘可能與 Object 或 Array 相似。 Immutable.js 壓縮後下載有 16K。

其中有 3 種最重要的數據結構說明一下:(Java 程序員應該最熟悉了)

  • Map:鍵值對集合,對應於 Object,ES6 也有專門的 Map 對象

  • List:有序可重複的列表,對應於 Array

  • Set:無序且不可重複的列表

簡單示例

import { Map } from "immutable";
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50

seamless-immutable

seamless-immutable是另外一套持久化數據結構的庫,它並無實現完整的 Persistent Data Structure,而是使用 Object.defineProperty(所以只能在 IE9 及以上使用)擴展了 JavaScript 的 Array 和 Object 對象來實現,只支持 Array 和 Object 兩種數據類型,API 基於與 Array 和 Object ,所以許多不用改變本身的使用習慣,對代碼的入侵很是小。同時,它的代碼庫也很是小,壓縮後下載只有 2K。

簡單示例

// 使用  seamless-immutable.js 後
import Immutable from 'seamless-immutable';
var array = Immutable(["totally", "immutable", {hammer: "Can’t Touch This"}]);

array[1] = "I'm going to mutate you!"
array[1] // "immutable"

array[2].hammer = "hm, surely I can mutate this nested object..."
array[2].hammer // "Can’t Touch This"

for (var index in array) { console.log(array[index]); }
// "totally"
// "immutable"
// { hammer: 'Can’t Touch This' }

JSON.stringify(array) // '["totally","immutable",{"hammer":"Can’t Touch This"}]'

seamless-immutable的實現依賴於ECMAScript 5 的一些特性,如Object.definePropertyObject.freeze,所以會在瀏覽器兼容性方面有所欠缺:

image

不過這不是問題啦,可使用 polyfill es-shims/es5-shim 來解決。

對比

雖然 Immutable.js 儘可能嘗試把 API 設計的原生對象相似,有的時候仍是很難區別究竟是 Immutable 對象仍是原生對象,容易混淆操做。

Immutable 中的 Map 和 List 雖對應原生 Object 和 Array,但操做很是不一樣,好比你要用 map.get('key')而不是 map.keyarray.get(0) 而不是 array[0]。另外 Immutable 每次修改都會返回新對象,也很容易忘記賦值。

當使用外部庫的時候,通常須要使用原生對象,也很容易忘記轉換。

固然也有一些辦法來避免相似問題發生:

  • 使用 Flow 或 TypeScript 這類有靜態類型檢查的工具

  • 約定變量命名規則:如全部 Immutable 類型對象以 $$ 開頭。

  • 使用 Immutable.fromJS 而不是 Immutable.MapImmutable.List 來建立對象,這樣能夠避免 Immutable 和原生對象間的混用。

可是還有一個致命的問題是,對現有代碼的改造,使用 Immutable.js 成本實在太大。

seamless-immutable雖然數據結構和API不如Immutable.js豐富,可是對於只想使用Immutable Data來對React進行優化以免重複渲染的咱們來講,已是綽綽有餘了。並且Array和Object原生的方法等均可以直接使用,原有項目改動極小。

React中使用

因爲seamless-immutable的實現依賴於ECMAScript 5 和原生的Array、Object自然的兼容性,致使其在React中的使用很是簡單,只要注意三點就能夠達到效果:

初始化state

初始化state數據的時候,使用Immutable的初始化方式。

import Immutable from 'seamless-immutable';

state: {
    orderList: Immutable([]),
  }

修改state數據

修改state數據的時候,一樣也要注意:

saveOrderList(state, {payload: items}) {
      return {...state, orderList: Immutable(items)};
    }

shouldComponentUpdate

使用pure-render-decorator,真是方便、快捷又優雅。固然,因爲decorator屬於ES7的特性,babel還須要本身配置。

import React from 'react';
import pureRender from 'pure-render-decorator';

@pureRender
class OrderListView extends React.Component {
  render() {
    const {orderList} = this.props;
    return (
      <div>
        {
          orderList.map((item) => {
            return (
              <div key={item.orderNum}>
                <div>{item.orderNum}</div>
                <div>{item.createTime}</div>
                <div>{item.contact}</div>
                <hr/>
              </div>
            );
          })
        }
      </div>
    );
  }
}

export default OrderListView;

怎麼樣,傳說中的React的SCU的優化就是這麼簡單,趕忙去試試吧。

參考

相關文章
相關標籤/搜索