React 性能優化,你須要知道的幾個點

寫了一段時間的react以後,漸漸的喜歡上了使用react來寫應用。javascript

咱們知道,Facebook在推出react時打出的旗號之一就是高性能。php

今天咱們還一塊兒來聊一聊react的性能優化,思考還能經過哪些手段來提高React的性能,使咱們的react更快,性能更好。java

一,react組件的性能優化(渲染角度優化)

1,react性能查看工具

再講性能優化以前,咱們須要先來了解一下如何查看react加載組件時所耗費的時間的工具,在react 16版本以前咱們可使用React Perf來查看。react

你們能夠在chorme中先安裝React Perf擴展,而後在入口文件或者reduxstore.js中加入相應的代碼便可:git

React Perf

在最新的React16版本中,咱們能夠直接在url後加上?react_pref,就能夠在chrome瀏覽器的performance,咱們能夠查看User Timeing來查看組件的加載時間。github

react16.0_pref

使用此工具的具體操做你們能夠看下圖:算法


react16.0_pref.gif

2,單個react組件性能優化

2.1,render裏面儘可能減小新建變量和bind函數,傳遞參數是儘可能減小傳遞參數的數量。

首先咱們先思考一個問題,好比我要實現一個點擊按鈕使相應的num增長1,咱們有哪一些方法。chrome

你們應該都能想到,無非就是三種,以下圖:編程

react_function

第一種是在構造函數中綁定this,第二種是在render()函數裏面綁定this,第三種就是使用箭頭函數,都能實現上述方法;redux

可是哪種方法的性能最好,是咱們要考慮的問題。應該你們都知道答案:第一種的性能最好

由於第一種,構造函數每一次渲染的時候只會執行一遍;

而第二種方法,在每次render()的時候都會從新執行一遍函數;

第三種方法的話,每一次render()的時候,都會生成一個新的箭頭函數,即便兩個箭頭函數的內容是同樣的。

第三種方法咱們能夠舉一個例子,由於react判斷是否須要進行render淺層比較,簡單來講就是經過===來判斷的,若是state或者prop的類型是字符串或者數字,只要值相同,那麼淺層比較就會認爲其相同;

可是若是前者的類型是複雜的對象的時候,咱們知道對象是引用類型,淺層比較只會認爲這兩個prop是否是同一個引用,若是不是,哪怕這兩個對象中的內容徹底同樣,也會被認爲是兩個不一樣的prop

舉個例子:

當咱們給組件Foo給名爲styleprop賦值;

<Foo style={{ color:"red" }}
複製代碼

使用這種方法,每一次渲染都會被認爲是一個style這個prop發生了變化,由於每一次都會產生一個對象給style

那麼咱們應該如何改進,若是想要讓react渲染的時候認爲先後對象類型prop相同,則必需要保證prop指向同一個javascript對象,以下:

const fooStyle = { color: "red" }; //取保這個初始化只執行一次,不要放在render中,能夠放在構造函數中

<Foo style={fooStyle} />
複製代碼

這個問題是咱們在平時的編碼中能夠避免的。

2.2,定製shouldComponentUpdate函數

shouldComponentUpdate是決定react組件何時可以不從新渲染的函數,可是這個函數默認的實現方式就是簡單的返回一個true。也就是說,默認每次更新的時候都要調用所用的生命週期函數,包括render函數,從新渲染。

咱們來看一下下面的一個例子

shouldComponentUpdate

咱們寫兩個組件,AppDemo組件,並寫兩個方法,一個改變App中的num的值,一個是改變title,咱們在Demo的render中打印render函數。咱們能夠看到如下的效果:

shouldComponentUpdate_demo

咱們能夠清晰的看到雖然demo組件裏的title值沒有改變,可是仍是render了。

爲了解決這個問題,咱們能夠對demo組件進行以下的修改:

shouldComponentUpdate

只有當demo的title值發生改變的時候,咱們纔去render,咱們能夠看一下效果:

shouldComponentUpdate_demo

以上只是一個特別簡單的一個對於shouldComponentUpdate的定製。

在最新的react中,react給咱們提供了React.PureComponent,官方也在早期提供了名爲react-addons-pure-render-mixin插件來從新實現shouldComponentUpdate生命週期方法。

PureComponent

經過上述的方法的效果也是和咱們定製shouldComponentUpdate的效果是一致的。

可是咱們要注意的是,這裏的PureRender是淺比較的,由於深比較的場景是至關昂貴的。因此咱們要注意咱們在1.1中說到的一些注意點:不要直接爲props設置對象或者數組不要將方法直接綁定在元素上,由於其實函數也是對象

2.3,Immutable.js

先配上一張經典的圖和經典的一句話:

Immutable

Shared mutable state is the root of all evil(共享的可變狀態是萬惡之源)

-- Pete Hunt

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

舉個例子:

foo = { a : 1 };
bar = foo;
bar.a = 2;
複製代碼

當咱們給bar.a賦值後,會發現foo.a也變成了2,雖然咱們能夠經過深拷貝與淺拷貝解決這個問題,可是這樣作很是的昂貴,對cpu和內存會形成浪費。

這裏就須要用到Immutable,經過Immutable建立的Immutable Data一旦被建立,就不能再更改。對Immutable對象進行修改、添加或刪除操做,都會返回一個新的Immutable對象。

這裏咱們將一下其中三個比較重要的數據結構

  • Map:鍵值對集合,對應ObjectEs6種也有專門的Map對象
  • List:有序可重複列表,對應於Array
  • ArraySet:有序且不可重複的列表

咱們能夠看兩個例子:

使用Map生成一個immutable對象

import { Map , is } from 'immutable';

let obj = Map({
  'name': 'react study',
  'course': Map({name: 'react+redux'})
})

let obj1 = obj.set('name','darrell');

console.log(obj.get('course') === obj1.get('course')); // 返回true
console.log(obj === obj1); // 返回false
複製代碼

Immutable.is 比較的是兩個對象的 hashCodevalueOf(對於 JavaScript 對象)。因爲 immutable 內部使用了 Trie 數據結構來存儲,只要兩個對象的 hashCode 相等,值就是同樣的。這樣的算法避免了深度遍歷比較,性能很是好。

let obj = Map({name:1,title:'react'});
let obj1 = Map({name:1,title:'react'});
console.log(is(obj,obj1)); // 返回true

let obj2 = {name:1,title:'react'};
let obj3 = {name:1,title:'react'};
console.log(is(obj2,obj3)); // 返回false
複製代碼

Immutable優勢

  • 減小內存的使用
  • 併發安全
  • 下降項目的複雜度
  • 便於比較複雜數據,定製shouldComponentUpdate方便
  • 時間旅行功能
  • 函數式編程

Immutable缺點

  • 學習成本
  • 庫的大小(建議使用seamless-immutable)
  • 對現有項目入侵嚴重
  • 容易與原生的對象進行混淆

若是你們想深刻了解,能夠參考immutableIMMUTABLE 詳解

2.4,多個react組件性能優化,key的優化

react組件在裝載過程當中,react經過在render方法在內存中產生一個樹形結構,樹上的節點表明一個react組件或者原生的Dom元素,這個樹形結構就是咱們所謂的Vitural Dom,react根據這個來渲染產生瀏覽器的Dom樹。

react在更新階段對比原有的Vitural Dom和新生成的Vitural Dom,找出不一樣之處,在根據不一樣來渲染Dom樹。

react爲了追求高性能,採用了時間複雜度爲O(N)來比較兩個屬性結構的區別,由於要確切比較兩個樹形結構,須要經過O(N^3),這會下降性能。

咱們舉幾個狀況,你們就會立刻理解:

  • 節點類型不一樣

    // A組件
    
    <div>
      <Todos /> </div>
    
    // B組件
    <span>
      <Todos /> </span>
    複製代碼

    咱們想把A組件更新成B組件,react在作比較的時候,發現最外面的根結點不同,直接就廢掉了以前的<div>節點,包括裏面的子節點,這是一個巨大的浪費,可是爲了不O(N^3)的時間複雜度,只能採用這種方式

    因此在開發過程當中,咱們應該儘可能避免上面的狀況,不要將包裹節點的類型隨意改變。

  • 兩個節點類型同樣

    這裏包括兩種狀況,一種是節點是Dom類型,還有一種react組件。

    對於dom類型,咱們舉個例子:

    // A組件
    <div style={{color: 'red',fontSize:15}} className="welcome">
      Hello World!!!
    </div>
    
    // B組件
    <div style={{color: 'green',fontSize:15}} className="react">
      Good Bye!!!
    </div>
    複製代碼

    上述A和B組件的區別是文字、classNamestyle中的color發生改變,由於Dom元素沒變,React只會修改他變化的部分。

    針對react組件類型,渲染無非就是在走一遍組件實例的更新過程,最主要的就是定製shouldComponentUpdate,咱們上面也有講到,就不細講了。

  • 多個子組件狀況

    咱們看兩個例子就能明白

    例子一:

    // A
    <ul>
      <TodoItem text="First" complete={false} />
      <TodoItem text="Second" complete={false} />
    </ul>
    
    // B
    <ul>
      <TodoItem text="First" complete={false} />
      <TodoItem text="Second" complete={false} />
      <TodoItem text="Third" complete={false} />
    </ul>
    複製代碼

    從A變到B,若是shouldComponentUpdate處理得當,咱們只須要更新裝載third的那一次就行。

    咱們來看看下一個例子:

    // A
    <ul>
      <TodoItem text="First" complete={false} />
      <TodoItem text="Second" complete={false} />
    </ul>
    
    // B
    <ul>
      <TodoItem text="Zero" complete={false} />
      <TodoItem text="First" complete={false} />
      <TodoItem text="Second" complete={false} />
    </ul>
    複製代碼

    這裏由於react是採用O(n)的時間複雜度,因此會依次將text爲First的改成Zero,text爲Second改成First,在最後再加上一個組件,text爲Second。現存的兩個的text的屬性都被改變了,因此會依次渲染。

    若是咱們這裏有1000個實例,那麼就會發生1000次更新。

    這裏咱們就要用到Key

    簡單來講,其實這一個Key就是react組件的身份證號。

    咱們將上一個例子改爲以下,就能夠避免上面的問題了,react就可以知道其實B裏面的第二個和第三個組件其實就是A中的第一個和第二個實例。

    // A
    <ul>
      <TodoItem key={1} text="First" complete={false} />
      <TodoItem key={2} text="Second" complete={false} />
    </ul>
    
    // B
    <ul>
      <TodoItem key={0} text="Zero" complete={false} />
      <TodoItem key={1} text="First" complete={false} />
      <TodoItem key={2} text="Second" complete={false} />
    </ul>
    複製代碼

    不過如今,react也會提醒咱們不要忘記使用key,若是沒有加,在瀏覽器中會報錯。

    react_key

    關於key的使用咱們要注意的是,這個key值要穩定不變的,就如同身份證號之於咱們是穩定不變的同樣。

    一個常見的錯誤就是,拿數組的的下標值去當作key,這個是很危險的,代碼以下,咱們必定要避免。

    <ul>
      {
            todos.map((item, index) => {
                <TodoItem
                  key={index}
                  text={item.text}
                  completed={item.completed}
            })
      }
    </ul>
    複製代碼

二,redux性能優化:reselect(數據獲取時優化)

在前面的優化過程當中,咱們都是優化渲染來提升性能的,既然reactredux都是經過數據驅動的的方式驅動渲染過程,那麼處理優化渲染過程,獲取數據的過程也是須要考慮的一個優化點。

//下面是redux中簡單的一個篩選功能
const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}
複製代碼

mapStateToProps函數做爲redux store中獲取數據的重要一環,當咱們根據filtertodos來顯示相應的待辦事項的時候,咱們都要遍歷todos字段上的數組。

當數組比較大的時候,則會下降性能。

這個時候,reselect就應運而生了,它的動做原理:只要相關的狀態沒有改變,那麼就直接使用上一次的緩存結果。

具體的用法我就不在這裏過多介紹了,已經有不少的牛人寫了相關的文章,我也不重複寫了,你們若是想深刻了解的話,能夠參考reselect的giuhubRedux的中間件-Reselect

三:參考資料

此篇文章是參考了《深刻React技術棧》和《深刻淺出React與Redux》這兩本書中關於對react性能優化的章節,再加上本身的動手實踐與思考寫的。

文章中不乏會有些錯誤的地方還請你們多多批評指正。

做者:darrell 連接:https://www.jianshu.com/p/333f390f2e84 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索