React是一個(gè)專注UI層的框架驮审,它使用虛擬DOM技術(shù),以保證它UI的告訴喧染耻卡;使用單向數(shù)據(jù)流疯汁,因此它的數(shù)據(jù)綁定更加的簡(jiǎn)單;那么它內(nèi)部是如何保持簡(jiǎn)單高效的UI渲染呢劲赠?這種渲染機(jī)制有可能存在什么性能問(wèn)題呢涛目?
React組件渲染問(wèn)題引出
React不直接操作DOM,它在內(nèi)存中維護(hù)一個(gè)快速相應(yīng)的DOM描述凛澎,render方法返回一個(gè)DOM的描述霹肝,React能夠計(jì)算出兩個(gè)DOM描述的差異,然后更新瀏覽器中的DOM塑煎。這就是著名的DOM Diff.
也就是說(shuō)React在接受屬性(props)或者狀態(tài)(state)更新時(shí)沫换,就會(huì)通過(guò)前面的方式更新UI。所以React整個(gè)UI喧染是比較快的最铁,但是這里面可能出現(xiàn)的問(wèn)題是:
假設(shè)我們定義一個(gè)父組件讯赏,其包含了5000個(gè)子組件。我們有一個(gè)輸入框輸入操作冷尉,每次輸入一個(gè)數(shù)字漱挎,對(duì)應(yīng)的那個(gè)子組件的背景變紅。
<Components>
<Components-1 />
<Components-2 />
<Components-3 />
...
<Components-5000 />
</Components>
這樣我們?cè)谳斎霐?shù)字1雀哨,則子組件1的背景色變化磕谅,但是在這個(gè)過(guò)程中私爷,所有的子組件都進(jìn)行了重新渲染,導(dǎo)致整體渲染變慢膊夹,造成這種現(xiàn)象的原因是React中父組件更新默認(rèn)出發(fā)所有子組件更新衬浑。
同時(shí),我們經(jīng)常在便利列表元素的時(shí)候會(huì)遇到這樣的提示:
Warning: Each child in an array or iterator should have a unique "key" prop.
這就是我們要探討的兩個(gè)性能優(yōu)化點(diǎn):
- 1.父組件更新默認(rèn)觸發(fā)子組件更新
- 2.列表類(lèi)型的組件默認(rèn)更新方式非常復(fù)雜
React性能檢測(cè)工具
我們利用react-addons-pref進(jìn)行性能檢測(cè)放刨。引入的方式如下:
import Perf from 'react-addons-perf'
window.Perf = Perf // 掛載到全局變量方便使用
檢測(cè)方法工秩,在瀏覽器控制臺(tái)中輸入如下命令:
- 開(kāi)始記錄:Perf.start()
- 結(jié)束記錄:Perf.stop()
- 打印結(jié)果:printInclusive()
控制臺(tái)會(huì)以表格的形式展示出結(jié)果:
上圖記錄了每個(gè)組件的執(zhí)行耗時(shí),渲染次數(shù)等關(guān)鍵信息进统。我們可以有針對(duì)性的進(jìn)行優(yōu)化助币。
注意:生產(chǎn)環(huán)境不要引入Perf
React性能優(yōu)化原理
這是React官網(wǎng)對(duì)組件渲染機(jī)制的描述圖,其中綠色組件代表不需要更新麻昼,紅色組件需要更新奠支,影響更新的條件主要有SCU(shouldComponentUpdate)及DOM DIff結(jié)果。
我們?cè)賮?lái)看看 組件觸發(fā)更新的流程圖:
通過(guò)上述的流程圖抚芦,再對(duì)比喧染的圖解可以看到倍谜,React的性能瓶頸主要出現(xiàn)在DOM以及DOM Diff的過(guò)程。如果進(jìn)行性能優(yōu)化叉抡,關(guān)鍵在于:
- shouldComponentUpdata 階段判斷尔崔,如果屬性及狀態(tài)與上一次相同,這個(gè)時(shí)候很明顯UI不會(huì)變化褥民,也不需要執(zhí)行后續(xù)生成DOM季春,DOM Diff的過(guò)程了,可以提高性能消返。
- DOM Diff 階段優(yōu)化载弄,提高Diff的效率
如何提高組件的渲染效率
針對(duì)文章開(kāi)頭提出的兩個(gè)性能問(wèn)題,我們得到以下解決方案:
- 子組件執(zhí)行 shouldComponentUpdate 方法撵颊,自行決定是否更新
- 給列表中的組件添加key屬性
我們可以控制子組件的shouldComponentUpdate從而控制是否渲染:
shouldComponentUpdate(nextProps, nextState) {
// 如果當(dāng)前的value值與待更新不相等宇攻,才執(zhí)行更新
return this.props.value !== nextProps.value;
}
針對(duì)列表遍歷類(lèi)型,遍歷的時(shí)候添加唯一key屬性倡勇,對(duì)子組件進(jìn)行唯一識(shí)別逞刷,準(zhǔn)確知道要操作的子組件,提高DOM Diff的效率妻熊。
array.map(val, index) => {
return <span key={index}>{val}</span>
})
PureRenderMixin與PureComponent
為了提高React組件喧染性能夸浅,React針對(duì)組件的shouldComponentUpdate方法進(jìn)行了封裝處理,我們不需要在每個(gè)組件里面手動(dòng)編寫(xiě)shouldComponentUpdate扔役。
PureRenderMixin
React在之前版本提供了 PureRenderMixin 的mixin形式帆喇,其用法如下:
// react官方demo
import PureRenderMixin from 'react-addons-pure-render-mixin';
class FooComponent extends React.Component {
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
其原理就是重寫(xiě)了 shouldComponentUpdate 方法。
PureComponent
React 15.3.0 新增了一個(gè) PureComponent 類(lèi)亿胸,以 ES2015 class 的方式方便地定義純組件 (pure component)坯钦,用于取代之前的 PureRenderMixin法严。
這個(gè)類(lèi)的用法很簡(jiǎn)單,如果你有些組件是純組件葫笼,那么把繼承類(lèi)從 Component 換成 PureComponent 即可。當(dāng)組件更新時(shí)拗馒,如果組件的 props 和 state 都沒(méi)發(fā)生改變路星,render 方法就不會(huì)觸發(fā),省去 Virtual DOM 的生成和比對(duì)過(guò)程诱桂,達(dá)到提升性能的目的洋丐。
import React, { PureComponent } from 'react'
class Example extends PureComponent {
render() {
// ...
}
}
這里要注意的是:PureRenderMixin、PureComponent 內(nèi)進(jìn)行的僅僅是淺比較對(duì)象(shallowCompare)挥等。如果對(duì)象包含了復(fù)雜的數(shù)據(jù)結(jié)構(gòu)友绝,深層次的差異可能會(huì)產(chǎn)生誤判。比如肝劲,如果我們的state變?yōu)椋?/p>
state = {
value: { foo: 'bar' }
}
// 每次更改value值的時(shí)候進(jìn)行:
this.setState({ value: newValue });
此時(shí)直接通過(guò)值的比較是行不通的迁客,因?yàn)閷?duì)象的引用關(guān)系,導(dǎo)致在子組件里面接受到的 this.props.value 與 nextProps.value 永遠(yuǎn)都是相等的辞槐。這里的解決方案主要有:
- 深比較: 原理與深拷貝類(lèi)似掷漱,比較耗時(shí),不推薦
- immutable.js:FaceBook官方提出的不可變數(shù)據(jù)解決方案榄檬,主要解決了復(fù)雜數(shù)據(jù)在deepClone和對(duì)比過(guò)程中性能損耗
總結(jié)
雖然React提供了Virtual DOM DOM Diff 等優(yōu)秀的能力來(lái)提高渲染性能卜范,但是在實(shí)際使用過(guò)程中,我們經(jīng)常會(huì)遇到父組件更新鹿榜,不需要更新所以子組件的場(chǎng)景(分頁(yè))海雪,此時(shí)必須考慮利用React本周的渲染機(jī)制來(lái)進(jìn)行優(yōu)化。