前言
影響性能最大的因素是界面的重繪和重排版,React 背后的 Virtual DOM 就是盡可能地減少重繪與重排版荚藻。
我們需要提高 React Virtual DOM 的效率,從 React 渲染過程看崖蜜,如何防止不必要的渲染可能是最需要取解決的問題凄贩。針對這個問題,React 官方提供了一個便捷的方法來解決巾腕,那就是 PureRender。
純函數(shù)
要理解 PureRender 中的 Pure絮蒿,要從函數(shù)式編程的的基本概念 "純函數(shù)"講起尊搬。純函數(shù)由三大原則構(gòu)成。
- 給定相同的輸入土涝,他總是返回相同的輸出
對于一個方法佛寿,只要我們傳入的值是固定,無論做多少次調(diào)用回铛,結(jié)果都是一樣狗准。有些方法不依賴于你傳入的參數(shù),如 Math.random()茵肃,不傳任何參數(shù)到方法中腔长,該方法依然總是會輸出不同的結(jié)果,這種方法也是純函數(shù)验残。還有常用的 slice 和 splice 方法捞附,slice在參數(shù)一致的時候結(jié)果都是一致的,splice方法的執(zhí)行結(jié)果會改變原來數(shù)組您没,對于程序來說鸟召,splice 的隱藏行為是危險的,所以 splice 不是純函數(shù)氨鹏。
- 過程沒有副作用
在純函數(shù)中我們不能改變外部狀態(tài)欧募。
- 沒有額外的狀態(tài)依賴
方法內(nèi)的狀態(tài)都只在方法的生命周期內(nèi)存活,意味著不能在方法內(nèi)使用共享變量仆抵,這會給方法帶來不可知因素跟继。
純函數(shù)也是函數(shù)式編程的基礎(chǔ),他完全獨立于外部狀態(tài)镣丑,這樣就避免了因為共享外部狀態(tài)而導(dǎo)致的 bug舔糖。
純函數(shù)非常方便方法級別的測試以及重構(gòu),可以讓程序具有良好的擴展性及適應(yīng)性莺匠。
React 就是函數(shù)式編程金吗,React 組件本身就是純函數(shù),可以表示為 UI = f(data),data 包括 props 和 state摇庙,改變 data旱物,就能改變 UI,而不是像 JQuery 一樣直接操縱 UI跟匆。
可以通過拆分組件為自組件异袄,進(jìn)而對組件做更小顆粒度的控制。這是函數(shù)式編程的魅力之一玛臂,保持純凈狀態(tài),讓方法或組件更加專注 (focused)封孙,體積更小 (small)迹冤,更獨立(independent),更具有復(fù)用性 (reustability)虎忌。
PureRender
pureRender 指的就是組件滿足純函數(shù)條件泡徙,即組件的渲染是被相同的 props 和 state 渲染進(jìn)而得到相同的結(jié)果。
1. PureRender 的本質(zhì)
React 生命周期中有一個 shouldComponentUpdate 方法膜蠢,如果返回 true堪藐,表示需要渲染,返回 false 表示不需要渲染挑围。
2. 運用 PureRender
React 新版本提供一個 PureComponent礁竞,PureComponent 內(nèi)部重新實現(xiàn) shouldComponentUpdate 方法,讓當(dāng)前傳入的 props 和 state 與之前的作淺比較,杉辙,淺比較對 object 只作了引用比較模捂,并沒有做值比較,如果返回 false蜘矢,那么組件就不會執(zhí)行 render 方法狂男。
3. 優(yōu)化 PureRender
- 直接為props設(shè)置對象或數(shù)組
每次調(diào)用 React 組件其實都會重新創(chuàng)建組件,就算傳入的數(shù)組或?qū)ο蟮闹禌]有改變品腹,它們引用的地址也會發(fā)生改變岖食。
<TestComponent
style={{background: 'white'}}
/>
我們可以把傳入的數(shù)組或?qū)ο蟊4娉梢环菀谩?/p>
<TestComponent
style={styles.container}
/>
const styles = StyleSheet.create({
container: {
background: 'white'
},
});
- 設(shè)置 props 方法并通過事件綁定在元素上
onPress() {
}
<TestComponent
onPress={this.onPress.bind(this)}
/>
這樣寫,每一次渲染都會重新綁定 onPress方法, 不要讓方法每一次都綁定舞吭,因此把綁定移動到構(gòu)造器內(nèi)泡垃。
constructor(props) {
super(props);
this.onPress = this.onPress.bind(this);
}
onPress() {
}
render() {
<TestComponent
onPress={this.onPress}
/>
}
Immutable
在傳遞的數(shù)據(jù)的時候,可以直接使用 Immutable Data 來進(jìn)一步提高組件的渲染性能镣典。
1. Immutable Data
Immutable Data 就是一旦創(chuàng)建兔毙,就不能再更改的數(shù)據(jù)。對 Immutable 對象進(jìn)行修改兄春,添加或者刪除操作澎剥,都會返回一個新的 Immutable 對象。Immutable 實現(xiàn)的原理是持久化的數(shù)據(jù)結(jié)構(gòu),也就是使用舊數(shù)據(jù)創(chuàng)建新數(shù)據(jù)哑姚,要保證就數(shù)據(jù)同時可用切不變祭饭。同時為了避免深拷貝把所有節(jié)點都復(fù)制一遍帶來的性能損耗,Immtable 使用了結(jié)構(gòu)共享叙量,即如果對象樹中一個節(jié)點發(fā)生變化倡蝙,只修改這個節(jié)點和受他影響的父節(jié)點,其他節(jié)點進(jìn)行共享绞佩。
- Map:鍵值對集合寺鸥,對應(yīng)于Object。
- List:有序可重復(fù)的列表品山,對應(yīng)于Array胆建。
- ArraySet:無序切不可重復(fù)的列表。
2. Immutable 的優(yōu)點
- 降低了 "可變" 帶來的復(fù)雜度肘交。
- 節(jié)省內(nèi)存笆载。Immutable 使用結(jié)構(gòu)共享盡量復(fù)用內(nèi)存。沒有被引用的對象會被垃圾回收涯呻。
- 撤銷/重做凉驻,復(fù)制/粘帖,甚至?xí)r間旅行這些功能做起來都變得簡單复罐。因為每次數(shù)據(jù)都是不一樣的涝登,那么只要把這些數(shù)據(jù)都放到一個數(shù)組里存儲起來,想回退到哪里市栗,就拿出對應(yīng)的數(shù)據(jù)缀拭。
- 并發(fā)安全。Immutable 數(shù)據(jù)天生不可變填帽,但 js 是單線程運行蛛淋,現(xiàn)在并沒有什么用。
- 擁抱函數(shù)式編程篡腌。只要輸入一致褐荷,那么輸出必然一致,這樣開發(fā)的組件更易于調(diào)試和組裝嘹悼。
3. 使用 Immutable 的缺點
容易與原生對象混淆叛甫。
4. Immutable.is
為了直接比較對象的值,Immutable提供了Immutable.is 來作"值比較"
const map1 = Immutable.Map({a: 1, b: 1})
const map2 = Immutable.Map({a: 1, b: 1})
map1 === map2; // => false
Immutable.is(map1, map2); // => true
Immutable.is比較的是兩個對象的hashCode 或 valueOf (對于JS 對象)杨伙,由于Immutable 內(nèi)部使用 trie 數(shù)據(jù)結(jié)構(gòu)來存儲其监,只要兩個對象 hashCode 相等,值就是一樣的限匣。
5. Immutable 和 PureRender
React 做性能優(yōu)化最常用的就是 shouldComponentUpdate 方法抖苦,但他默認(rèn)返回true,即始終會執(zhí)行 render 方法,然后做 Virtual DOM比較锌历,并得出是否需要做真實 DOM 更新贮庞,這里往往帶來不必要的渲染,
當(dāng)然究西,我們可以在 shouldComponentUpdate 中使用深拷貝和深比較來避免無必要的 render窗慎,但是深拷貝和深比較一般都是非常昂貴的選擇。
Immutable.js 提供了簡潔卤材,高效的判斷數(shù)據(jù)是否變化的方法遮斥,只需要 is 比較就能知道是否需要執(zhí)行 render,而這個操作幾乎零成本商膊,所以可以極大提高性能伏伐。修改后的 shouldComponentUpdate 是這樣的。
import React, {
Component
} from 'react';
import Immutable from 'immutable';
class BaseComponent extends Component {
shouldComponentUpdate(nextProps, nextState = {}) {
return !Immutable.is(Immutable.fromJS(this.props), Immutable.fromJS(nextProps))
|| !Immutable.is(Immutable.fromJS(this.state), Immutable.fromJS(nextState));
}
}
export default BaseComponent;
而在自定義組件你只需要繼承BaseComponent晕拆,就可以攔截不必要的渲染。
class App extends BaseComponent {
}
6. key
寫動態(tài)組件的時候材蹬,需要給動態(tài)子組件添加 key props实幕,否則會報一個警告。Key 要保持惟一堤器,穩(wěn)定昆庇。
key 的作用是為了 Virtual DOM 在執(zhí)行 diff 算法時更快地找到對應(yīng)的節(jié)點,提高 diff 速度闸溃。