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

一缩举、PureRender

影響網(wǎng)頁性能最大的因素是瀏覽器的重繪和排版,React的 Virtual DOM 就是為了盡量減少瀏覽器的重繪和重排版棒旗。要優(yōu)化性能誓篱,就需要提高 Virtual DOM 的效率。結(jié)合React的渲染過程來看笼沥,就是要 防止不必要的渲染蚪燕。 對(duì)此,React提供了一個(gè)便捷方法:PureRender奔浅。要理解PureRender中的Pure馆纳,首先需要理解純函數(shù)。

純函數(shù)的三大原則:

  • 給定相同的輸入汹桦,總是返回相同的輸出鲁驶。
  • 過程沒有副作用:在純函數(shù)中不能改變外部狀態(tài)
  • 沒有額外的狀態(tài)依賴:方法內(nèi)的狀態(tài)都只在方法的生命周期內(nèi)存活,意味著我們不能在方法內(nèi)使用共享變量营勤。

React組件本身就是純函數(shù)灵嫌。React的createElement方法保證了組件是純潔的,即傳入指定props得到一定的Virtual DOM葛作,整個(gè)過程是可預(yù)測的寿羞。PureRender中的 Pure 指的就是組件滿足純函數(shù)的條件,即組件的渲染是被相同的 props 和 state 渲染進(jìn)而得到相同的結(jié)果赂蠢。

PureRender的本質(zhì)是:重新實(shí)現(xiàn)了shouldComponentUpdate生命周期方法绪穆,讓當(dāng)前傳入的props和state與之前的作 淺比較,如果返回false,組件就不執(zhí)行render方法虱岂。

為什么是淺比較呢玖院?因?yàn)樯畋容^太昂貴了。故 PureRender 對(duì)object只作了引用比較第岖,并沒有進(jìn)行值比較难菌。在PureRender的源代碼中,只對(duì)新舊props進(jìn)行了淺比較蔑滓。其示例代碼如下:

function shallowEqual(obj, newObj){
    if(obj === newObj){
        return true;
    }

    const objKeys = Object.keys(obj);
    const newObjKeys = Object.keys(newObj);
    if(objKeys.length !== newObjKeys.length){
        return false;
    }

    //關(guān)鍵代碼郊酒,值關(guān)注props中每一個(gè)是否相等遇绞,無需深入判斷
    return objKeys.every(key=>{
        return newObj[key] === obj[key];
    })
}

1、運(yùn)用PureRender

利用createClass構(gòu)建組件時(shí)燎窘,可以使用官方的插件react-addons-pure-render-mixin摹闽。如下:

import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';

React.createClass({
    mixins: [PureRenderMixin],

    render(){
        return <div>foo</div>;
    }
});

用ES6 classes語法(不支持mixin)一樣可以使用這個(gè)插件:

import React, { Component } from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';

class APP extends Component {
    constructor(props){
        super(props);

        this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind;(this);
    }
    render(){
        return <div>foo</div>;
    }
}

當(dāng)然,也可以使用decorator來實(shí)現(xiàn)褐健,其中pure-render-deecorator庫已經(jīng)實(shí)現(xiàn)了這個(gè)功能付鹿。

2、優(yōu)化PureRender

淺比較可以覆蓋的場景并不太多蚜迅,當(dāng)props或state中有以下幾種情況時(shí)舵匾,無論如何都會(huì)觸發(fā)PuerRender為true(觸發(fā)組件重新渲染).

(1)直接將props值設(shè)置為對(duì)象或數(shù)組

例如:

<Comp style={{ color: 'red' }} />

實(shí)際上在React中,每次調(diào)用React組件其實(shí)都會(huì)創(chuàng)建新的組件慢叨。因此纽匙,就算每次傳入的對(duì)象或數(shù)組的值是相同的,它們的引用地址卻是不一樣的拍谐。也即是說烛缔,上面示例中每次渲染的style其實(shí)都是一個(gè)新的對(duì)象。為style prop設(shè)置一個(gè)默認(rèn)值(空對(duì)象)也是同樣的道理:

<Comp style={ this.props.style || {} } />

解決方法:提前賦值成常量轩拨,不直接使用字面量即可践瓷。

const defaultStyle = {} ;
<Comp style={ this.props.style || defaultStyle } />

這個(gè)方法將默認(rèn)值保存成了同一份引用,故而避免上述問題亡蓉。

同理晕翠,在props中為對(duì)象或數(shù)據(jù)計(jì)算新值同樣會(huì)觸發(fā)PureRender為true。

<Item item={ this.props.items.filter(item=>item.val > 30) } />

我們可以馬上想到:始終讓對(duì)象或數(shù)組保持在內(nèi)存中就可以增加命中率砍濒。但 保持對(duì)象引用 不符合函數(shù)式編程的原則淋肾,會(huì)為函數(shù)帶來副作用。后面介紹的Immutable.js優(yōu)雅的解決了這個(gè)問題爸邢。

(2)設(shè)置props方法并通過事件綁定在元素上

示例:

constructor(props){
    super(props);

    this.handleChange = this.handleChange.bind(this);
}

handleChange(e){
    this.props.update(e.target.value);
}

render(){
    return <Input onChange={this.handleChange} />
}

此處的優(yōu)化思路為:在構(gòu)造器內(nèi)進(jìn)行事件綁定樊卓,避免每次都綁定事件。如果綁定方法需要傳遞參數(shù)杠河,可以考慮通過抽象子組件或改變現(xiàn)有數(shù)據(jù)結(jié)構(gòu)碌尔。

(3)設(shè)置子組件

對(duì)于設(shè)置了子組件的React組件,在調(diào)用shouldComponentUpdate時(shí)券敌,均會(huì)返回true唾戚。

class ItemParent extends Component {
    render(){
        return(
            <Item>
                <span> hello </span>
            </Item>
        );
    }
}
上面的Item組件相當(dāng)于:
<Item 
    children={React.createClass('span', {}, 'hello')}
/>

顯然,Item組件在任何情況下都會(huì)重新渲染待诅。要避免這種情況叹坦,需要將判斷提到父級(jí)。也就是給ItemParent設(shè)置PureRender卑雁。

import React, { Component } from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';

class ItemParent extends Component {
    constructor(props){
        super(props);
        this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);b
    }
    render(){
        return(
            <Item>
                <span> hello </span>
            </Item>
        );
    }
}

二募书、Immutable

在傳遞數(shù)據(jù)時(shí)轧钓,可以直接使用Immutable Data來進(jìn)一步提升組件的渲染性能。

Js中的對(duì)象一般是可變的(mutable)锐膜,因?yàn)槭褂昧艘觅x值道盏,新的對(duì)象簡單引用了原始對(duì)象粹排,改變新的對(duì)象會(huì)影響到原始對(duì)象。使用引用賦值是為了節(jié)約內(nèi)存,但當(dāng)應(yīng)用復(fù)雜后限次,就造成了非常大的隱患。為了解決這個(gè)問題,一般的做法是使用深拷貝或淺拷貝來避免被修改,但這樣又會(huì)造成CPU和內(nèi)存的浪費(fèi)长豁。Immutable可以很好的解決這些問題钝侠。

1、Immutable Data

Immutable Data就是一旦創(chuàng)建叮阅,就不能更改的數(shù)據(jù)。對(duì)Immutable對(duì)象進(jìn)行修改、添加或刪除操作眯分,都會(huì)返回一個(gè)新的Immutable對(duì)象暑劝。

Immutable的原理:持久化的數(shù)據(jù)結(jié)構(gòu),即:使用舊數(shù)據(jù)創(chuàng)建新數(shù)據(jù)時(shí)颗搂,要保證舊數(shù)據(jù)同時(shí)可用且不變担猛。同時(shí)為了避免深拷貝把所有節(jié)點(diǎn)復(fù)制一遍帶來的性能損耗,Immutable使用了結(jié)構(gòu)共享丢氢,即:如果對(duì)象樹中的一個(gè)節(jié)點(diǎn)發(fā)生變化傅联,只修改這個(gè)節(jié)點(diǎn)和受它影響的父節(jié)點(diǎn),其他節(jié)點(diǎn)則進(jìn)行共享疚察。

2蒸走、Immutable的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

  1. 降低了“可變”帶來的復(fù)雜性:值不可變。
  2. 節(jié)省內(nèi)存:使用共享結(jié)構(gòu)盡量復(fù)用內(nèi)存貌嫡,沒有被引用的對(duì)象會(huì)被垃圾回收比驻。
  3. 非常便于實(shí)現(xiàn)撤銷/重做、粘貼/復(fù)制岛抄、時(shí)間旅行等功能:因?yàn)槊看螖?shù)據(jù)都是不一樣的别惦,只要把這些數(shù)據(jù)放在一個(gè)數(shù)組中存儲(chǔ)起來,想回退到哪里夫椭,就拿出對(duì)應(yīng)的數(shù)據(jù)掸掸,很容易開發(fā)撤銷/重做功能。
  4. 并發(fā)安全:數(shù)據(jù)不可變,就不再需要并發(fā)鎖扰付。
  5. 擁抱函數(shù)式編程:Immutable本身就是函數(shù)式編程中的概念堤撵。只要輸入一致,輸出必然一致羽莺。這樣開發(fā)的組件便于調(diào)試和組裝实昨。

缺點(diǎn):

Immutable最大的問題就是:容易與原生對(duì)象發(fā)生混淆。雖然Immutable已經(jīng)盡量把API設(shè)計(jì)得與原生對(duì)象相似盐固,但還是很難區(qū)分 Immutable對(duì)象 還是 原生對(duì)象屠橄。

Immutable中的 Map 與 List 雖然對(duì)應(yīng)的是 JS 中的 Object 和 Array,但操作完全不同闰挡。比如取值時(shí)要用 map.get('key') 而非 map.key ,要用 array.get(0) 而非array[0]。另外礁哄,Immutable每次修改都會(huì)返回新對(duì)象长酗,很容易忘記賦值

使用第三方庫時(shí)桐绒,一般需要使用原生對(duì)象夺脾,同樣容易忘記轉(zhuǎn)換對(duì)象

以下辦法可以避免類似問題的發(fā)生:

  • 使用FlowType 或 TypeScript 靜態(tài)類型檢查工具
  • 約定變量命名規(guī)則茉继,如所有的Immutable類型對(duì)象都以 $$ 開頭
  • 使用 Immutable.fromJS 而不是 Immutable.Map 或 Immutable.list 來創(chuàng)建對(duì)象咧叭,可以避免Immutable對(duì)象和原生對(duì)象間的混用。

3烁竭、Immutable.js

兩個(gè) Immutable 對(duì)象可以使用 === 比較菲茬,這樣是直接比較內(nèi)存地址,性能最好派撕。但是即使是兩個(gè)對(duì)象的值是一樣的婉弹,也會(huì)返回false。

    let map1 = Immutable.Map({a:1, b:2});
    let map2 = Immutable.Map({a:1, b:2});
    map1 === map2 // false

為了直接比較對(duì)象的值终吼,Immutable 提供了 Immutable.is 來作 “值比較”:

Immutable.is(map1, map2) // true

Immutable.is 比較的是兩個(gè)對(duì)象的 hashCode 或 valueOf (對(duì)于JS對(duì)象)镀赌。Immutable內(nèi)部使用了 trie 數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ),只要兩個(gè)對(duì)象的 hashCode 相等际跪,值就是一樣的商佛。這樣做避免了深度遍歷比較,性能非常好姆打。

React做性能優(yōu)化最常用的是 shouldComponentUpdate, 但它默認(rèn)返回 true,即始終會(huì)執(zhí)行 render 方法良姆,然后做 Virtual DOM 比較,得出是否需要做真實(shí) DOM 的更新幔戏,這往往會(huì)帶來很多不必要的渲染歇盼。當(dāng)然,也可以在 shouldComponentUpdate中使用深拷貝和深比較來避免不必要的 render, 但對(duì)性能消耗較大评抚。Immutable.JS提供了簡潔高效的判斷數(shù)據(jù)是否變化的方法豹缀,只需 === 和 Immutable.is 比較就能知道是否需要執(zhí)行render伯复, 這個(gè)操作幾乎零成本,可以極大提高性能邢笙。

三啸如、key

子組件如果是一個(gè)數(shù)組或者迭代器的話,那么必須有一個(gè)唯一的 key 屬性氮惯,用于標(biāo)識(shí)當(dāng)前項(xiàng)的唯一性叮雳。key 相同的情況下,React只會(huì)渲染第一個(gè)相同 Key 的項(xiàng)妇汗。

key用于做 Virtual DOM diff帘不,可以提高性能。key的取值原則為:獨(dú)一無二杨箭,能不用遍歷或隨機(jī)值就不用寞焙,除非列表內(nèi)容也并不是唯一的表示,且沒有可以相匹配的屬性互婿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捣郊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子慈参,更是在濱河造成了極大的恐慌呛牲,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驮配,死亡現(xiàn)場離奇詭異娘扩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)壮锻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門畜侦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人躯保,你說我怎么就攤上這事旋膳。” “怎么了途事?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵验懊,是天一觀的道長。 經(jīng)常有香客問我尸变,道長义图,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任召烂,我火速辦了婚禮碱工,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己怕篷,他們只是感情好历筝,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著廊谓,像睡著了一般梳猪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蒸痹,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天春弥,我揣著相機(jī)與錄音,去河邊找鬼叠荠。 笑死匿沛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的榛鼎。 我是一名探鬼主播逃呼,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼借帘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起淌铐,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤肺然,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后腿准,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體际起,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年吐葱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了街望。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弟跑,死狀恐怖灾前,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情孟辑,我是刑警寧澤哎甲,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站饲嗽,受9級(jí)特大地震影響炭玫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜貌虾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一吞加、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦衔憨、人聲如沸叶圃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盗似。三九已至,卻和暖如春平项,著一層夾襖步出監(jiān)牢的瞬間赫舒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工闽瓢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留接癌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓扣讼,卻偏偏與公主長得像缺猛,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子椭符,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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