一缩举、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):
- 降低了“可變”帶來的復(fù)雜性:值不可變。
- 節(jié)省內(nèi)存:使用共享結(jié)構(gòu)盡量復(fù)用內(nèi)存貌嫡,沒有被引用的對(duì)象會(huì)被垃圾回收比驻。
- 非常便于實(shí)現(xiàn)撤銷/重做、粘貼/復(fù)制岛抄、時(shí)間旅行等功能:因?yàn)槊看螖?shù)據(jù)都是不一樣的别惦,只要把這些數(shù)據(jù)放在一個(gè)數(shù)組中存儲(chǔ)起來,想回退到哪里夫椭,就拿出對(duì)應(yīng)的數(shù)據(jù)掸掸,很容易開發(fā)撤銷/重做功能。
- 并發(fā)安全:數(shù)據(jù)不可變,就不再需要并發(fā)鎖扰付。
- 擁抱函數(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)容也并不是唯一的表示,且沒有可以相匹配的屬性互婿。