05|React組件的性能優(yōu)化

本文主要是針對性能優(yōu)化做介紹,對應的React本身性能就比較不錯!要介紹的點如下:

  • 單個React組件的性能優(yōu)化
  • 多個React組件的性能優(yōu)化
  • 利用reselect提高數(shù)據(jù)選取的性能

因為現(xiàn)代化的Web應用的開發(fā)都是 組件化開發(fā)模式 ,因此我們不可能站在全局的角度去優(yōu)化,而是針對Web應用的核心部分,也就是 組件 針對性的進行優(yōu)化操作!

01|單個React組件性能優(yōu)化

  • Virtual DOM幫助React組件提高渲染性能

    • 雖然每次都是重新渲染,但是利用React最核心的功能,DIFF機制進行diff之后 渲染該渲染的部分!
    • 雖然說每次的DOM操作量比較少,但是計算和比較的時候依然是一個比較復雜的過程! 這樣一來就可以知道,在使用Virtual DOM之前就確認渲染結(jié)果不會有變化! 提升性能的方法不是diff而是不渲染甚至是不進行Diff!
  • 對性能進行檢測,對對應的部分進行針對性的優(yōu)化!

  • 避免過早優(yōu)化,我們應該對性能影響最關(guān)鍵的代碼進行優(yōu)化!

  • 使用提供的 shouldComponentUpdate 手動操作組件是否渲染! 更加細粒度的控制組件的渲染邏輯!

  • react-redux提供的 shouldComponentUpdate 對比prop和上一次使用到的prop上面,用的盡量簡單的方法! 也只是對應的淺層比較,如果說對應的porp值是復雜的對象,只看是不是同一對象的引用,如果不是,哪怕這兩個對象的內(nèi)容完全一樣,也會被認為兩個不同的prop!

為什么不使用深層比較呢?

  • 一個對象到底有多少層是無法預料的,如果使用遞歸對每個字段進行深層比較,讓代碼復雜的同時,也讓性能比較低下!

  • 對應的props如果說是對象類型的話, 想讓react-redux認為前后的對象類型prop是相同的,就必須要保證只想的同一對象!

    • 比如說是style屬性 可以使用一個對象對樣式進行設置,那么就可以將對應的 樣式進行抽象成一個變量進行使用!

對應的 關(guān)于事件的處理,如果說每次都是以箭頭函數(shù)的方式進行處理的話, 每次都是產(chǎn)生匿名函數(shù)! 匿名函數(shù)也就是每次都是新的函數(shù) 因此如果涉及到組件被應用到 多個頁面中內(nèi)存的占用就非常高!

對應的我們通過具體的代碼可以很清楚的復現(xiàn)這個知識點:

import React,{PropTypes} from "react";
import {toogleTodo,removeTodo} from "../actions";
const TodoList = ({todos,onToggleTodo,onRemoveTodo})=>{
    return (
        <ul className='todo-list'>
            todos.map((item)=>{
                <TodoItem key={item.id} text={item.text} completed={item.completed} 
                    onToggle={()=>onToogleTodo(item.id)}
                    onRemove={()=>onRemoveTodo(item.id)} />
            })
        </ul>
    );
}

對應的TodoItem點擊了對應的按鈕,調(diào)用父類的回調(diào)函數(shù),父類產(chǎn)生一個新的TodoItem 對應的每次更新都躲不過重新渲染的命運!

02|多個React組件的性能優(yōu)化

多個React組件的優(yōu)化,首先就需要學會從React的生命周期入手,從對應的生命周期階段針對性的去優(yōu)化!

render=>start: render method
op1=>operation: VDOM Tree
op2=>operation: RC or DOM
end=>end: View
action=>inputoutput: Action
render->op1->op2->end->action->op1
  • render方法在內(nèi)存中產(chǎn)生了樹形的結(jié)構(gòu)(Virtual DOM)
  • 樹上的每一個節(jié)點表示: React組件或者說是原生的DOM元素
  • React 根據(jù) Virtual DOM渲染瀏覽器中的DOM樹!

如果說用戶的交互出發(fā)了頁面的更新,網(wǎng)頁中需要更新頁面的話,React 依然通過render方法獲得了一個樹形結(jié)構(gòu)VirtualDOM 這個時候不能完全和裝載過程一樣直接使用VirtualDOm去產(chǎn)生DOM樹!

  • 這個階段(更新階段)巧妙的對比原有的Virtual DOM和新生成的Virtual DOM找出兩者的不同之處,根據(jù)不同來更新DOM! 只做必要的最小的改動!

  • 對應的這個找不同的過程叫做 Reconciliation 調(diào)和!

對照計算機科學目前的算法研究成果,比對N個節(jié)點的樹形結(jié)構(gòu)的算法,時間復雜度是O(N^3) 如果說比對兩個100節(jié)點的DOM樹需要計算 100*100*100 次 其中的算法復雜度 N表示節(jié)點數(shù),

但是對應的React實際采用的算法需要的時間復雜度為O(N) ,對比兩個樹形怎么著都要比對兩個樹形上的節(jié)點!

也不存在比O(N)復雜度更低的算法

對應的調(diào)和過程:

  • 節(jié)點類型不同的情況
    • React會直接丟棄原來樹形結(jié)構(gòu),然后重建DOM樹,對應的React組件也會經(jīng)歷卸載的生命周期
    • 面對不同的應用場景,可能會引發(fā)樹結(jié)構(gòu)某些組件的卸載和裝載過程!
  • 節(jié)點類型相同的情況
    • 如果兩個數(shù)形結(jié)構(gòu)的根節(jié)點類型相同,React就認為原來的根節(jié)點只需要更新過程,不會將其卸載,也不會引發(fā)根節(jié)點的重新裝載!
      • 對應的節(jié)點類型分為兩種:一種是DOM元素對應的也就是所謂的HTML元素,另一種則是 React的組件,也就是利用React庫定制的類型!

對應的如果說屬性結(jié)構(gòu)的根節(jié)點不是DOM元素,那就只可能是React組件類型,那么React做的工作類似,React此時也不知道如何更新DOM樹,因此邏輯還在React組件之中,React能做的也就是通過新節(jié)點的props更新原來的組件實例,引發(fā)組件實例更新的過程! 按照順序觸發(fā):

  1. shouldComponentUpdate 如果不需要更新的話,可以在函數(shù)中直接返回false來保持最大的性能!
  2. componentWillReceiveProps
  3. componentWillUpdate
  4. render
  5. componentDidUpdate
01|如果說多個子組件的情況是怎么樣的呢?

我們那一個最簡單的TodoItem來舉例吧:

<ul>
    <TodoItem text="first" completed={false}  />
    <TodoItem text="second" completed={false} />
</ul>

在更新之后,用JSX表示是這樣:

<ul>
    <TodoItem text="first" completed={false}  />
    <TodoItem text="second" completed={false} />
    <TodoItem text="Thrid" completed={false} />
</ul>

對應的結(jié)果就是,react檢查多出了一個TodoItem,創(chuàng)建一個新的TodoItem組件實例,該實例需要經(jīng)歷裝載的過程,但是對于前面兩個TodoItem實例,React會引發(fā)他們的更新過程! 但是如果說對應的shouldComponentUpdate函數(shù)實現(xiàn)恰當,props檢查之后就返回false之后,可以避免實質(zhì)的更新操作!

  • 剛剛那樣是在后面加了一個TodoItem實例,如果說在前面加的話又會出現(xiàn)什么問題呢?
<ul>
    <TodoItem text="zero" completed={false}  />
    <TodoItem text="first" completed={false} />
    <TodoItem text="second" completed={false} />
</ul>

和之前的代碼實例相比,此時的React會這么處理:

  • 首先認為把text從first改為了zero
  • second改為了first
  • 最后多出了一個TodoItem實例內(nèi)容為second

現(xiàn)存(first,second)的兩個實例的屬性被改變了,強迫他們完成了一個更新! 雖然這種情況只是改變了2個組件的屬性,如果說有一百個TodoItem實例的話,明顯就是一個浪費!

后面React提供了方法來克服這種浪費,于是有了key

02|Key的用法

React中,需要確定每一個組件在組件序列中的唯一標識就是它的位置! 因此React本身也不懂哪些子組件實質(zhì)上面沒有改變! key就是每一個組件的唯一標識!

<ul>
    <TodoItem key={1} text="zero" completed={false}  />
    <TodoItem key={2} text="first" completed={false} />
    <TodoItem key={3} text="second" completed={false} />
</ul>
  • key的值需要保證唯一性
  • 通過key的使用配合shouldComponentUpdate就能夠一定程度上面提高性能!
03|使用reselect提高數(shù)據(jù)獲取性能

對應的除了通過優(yōu)化渲染過程來提高性能,既然React和Redux都是通過數(shù)據(jù)驅(qū)動渲染過程,除了渲染過程,獲取數(shù)據(jù)的過程也是一個需要考慮的優(yōu)化點!

通過mapStateToProps函數(shù)從Redux store提供的state中產(chǎn)生渲染需要的數(shù)據(jù),對應的代碼如下所示:

const selectVisibleTodos = (todo,filter)=>{
    switch(filter){
        case FilterType.ALL:
            return todos;
        case FilterType.COMPLETED:
            return todos.filter(item=>item.completed);
        case FilterTypes.UNCOMPPLETED:
            return todos.filter(item=>!item.completed);
        default:
            throw new Error("unsupport filter!");
    }
}
const mapStateToProps = state=>{
    return {todos:selectVisibleTodos(state.todos,state.filter)};
}

Redux Store上獲取數(shù)據(jù)的重要一環(huán),mapStateToProps函數(shù)一定要快,從代碼來看,運算本身沒有什么課優(yōu)化的空間,

獲取對應的待辦事項,需要通過對應的todos和filter兩個字段的值計算出來! 計算過程需要遍歷todos字段上的數(shù)組,數(shù)組比較大的時候,TodoList組件的每一次重新渲染都需要重新計算一遍,負擔就會過重!

  • 兩階段選擇過程

對應的selectVisibleTodos函數(shù)的計算必不可少,那么對應的如何優(yōu)化呢?

并不是每一次TodoList的渲染都需要執(zhí)行selectVisibleTodos中的計算過程,如果對應的Redux Store狀態(tài)樹上的待辦事項的todos字段沒有變化,而代表當前過濾器的filter字段也沒有變化,實在沒有必要重新渲染todos數(shù)組來計算一個新的結(jié)果! 如果說上一次的結(jié)果能夠被緩存過來的話,那么就重用緩存就行了!

reselect庫的工作原理就是,只要相關(guān)狀態(tài)沒有改變的話,那就直接重用上一次的緩存!

reselect庫被用來創(chuàng)造 選擇器:接受一個state作為參數(shù),并且通過選擇器返回的函數(shù)的數(shù)據(jù)就是我們某個mapStateToProps需要的結(jié)果! 但是選擇器不是純函數(shù),一種有記憶力的函數(shù),運行選擇器函數(shù)會有副作用!

  • 通過輸入?yún)?shù)state抽取第一層結(jié)果,降低一層結(jié)果與之前的結(jié)果進行比對,如果完全相同沒必要進行比對,這一部分的比較,就是JavaScript中的全等操作符比較! 如果是對象且是同一個對象才會被認為是相同! 否則進入到下一步
  • 接下來的就是確定選擇器步驟一和步驟二分別進行什么計算,原則很簡單:
    • 步驟一:盡量快,運算非常簡單的,最好就是一個映射運算 通常是state參數(shù)中某個字段的引用!
    • 之后的活交給第二步去計算!

對上面的代碼進行改造就需要使用 reselect 庫:

import {createSelector} from "reselect";
import {FilterTypes} from "../constants.js";
const selectVisibleTodos = createSelector([getFilter,getTodos],(filter,todos)=>{
    switch(filter){
        case FilterTypes.ALL:
            return todos;
        case FilterTypes.COMPLETED:
            return todos.filter(item=>item.completed);
        case FilterTypes.UNCOMPLETED:
            return todos.filter(item=>!item.completed);
        default:
            throw new Error("unsupport filter!");
    }
})
const getFilter = state=> state.filter;
const getTodos = state=> state.todos;
import {selectVisibleTodos}  from "../selector.js";
const mapStateToProps = state=>{
    return {todos:selectVisibleTodos(state)};
}

這樣一來雖然說createSelector接受的所有函數(shù)都是為純函數(shù),但是選擇器有記憶的副作用,只要對應的state沒有變化自然輸出也沒有變化!

只要是Redux store狀態(tài)樹上的filter和todos字段不變的話,怎么觸發(fā)TodoList的渲染過程,都不會觸發(fā)遍歷todos字段的計算,性能自然更快!

  • 狀態(tài)樹的設計盡量的范式化,按照一定的設計規(guī)約he關(guān)系型數(shù)據(jù)庫的設計原則,減少數(shù)據(jù)的冗余!(數(shù)據(jù)冗余造成的后果就是難以保證數(shù)據(jù)的一致性!)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邑商,一起剝皮案震驚了整個濱河市菩颖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌巍扛,老刑警劉巖肴捉,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腹侣,死亡現(xiàn)場離奇詭異,居然都是意外死亡每庆,警方通過查閱死者的電腦和手機筐带,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缤灵,“玉大人伦籍,你說我怎么就攤上這事∪觯” “怎么了帖鸦?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胚嘲。 經(jīng)常有香客問我作儿,道長,這世上最難降的妖魔是什么馋劈? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任攻锰,我火速辦了婚禮,結(jié)果婚禮上妓雾,老公的妹妹穿的比我還像新娘娶吞。我一直安慰自己,他們只是感情好械姻,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布妒蛇。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绣夺。 梳的紋絲不亂的頭發(fā)上吏奸,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機與錄音陶耍,去河邊找鬼奋蔚。 笑死,一個胖子當著我的面吹牛物臂,可吹牛的內(nèi)容都是我干的旺拉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼棵磷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晋涣?” 一聲冷哼從身側(cè)響起仪媒,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谢鹊,沒想到半個月后算吩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡佃扼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年偎巢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兼耀。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡压昼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瘤运,到底是詐尸還是另有隱情窍霞,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布拯坟,位于F島的核電站但金,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏郁季。R本人自食惡果不足惜冷溃,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望梦裂。 院中可真熱鬧似枕,春花似錦、人聲如沸塞琼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至毅往,卻和暖如春牵咙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背攀唯。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工洁桌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侯嘀。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓另凌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親戒幔。 傳聞我的和親對象是個殘疾皇子吠谢,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容