動(dòng)機(jī)
當(dāng)使用React時(shí),在下一個(gè)steate或props更新時(shí),render()函數(shù)將會(huì)返回一個(gè)不同的React樹,接下來(lái)React會(huì)找出最高效地方式更新UI。
目前存在大量通用的方法能夠以最少的操作步驟將一個(gè)樹轉(zhuǎn)化成另外一棵樹。然而,這個(gè)算法是復(fù)雜度為O(n3),其中n 為樹中元素的個(gè)數(shù)。即如果有1000個(gè)元素,那么每次更新都要進(jìn)行10億次比較。
然而,React給予一下兩個(gè)假設(shè)實(shí)現(xiàn)了復(fù)雜度為O(n)的算法,即每次更新只需要進(jìn)行1000次的比較:
- 不同類型的兩個(gè)元素將會(huì)產(chǎn)生不同的樹。
- 開發(fā)人員可以使用一個(gè) key prop 來(lái)指示在不同的渲染中那個(gè)那些元素可以保持穩(wěn)定。
一. Diffing 算法
當(dāng)比較不同的兩個(gè)樹的時(shí)候,React首先比較兩個(gè)根元素。根據(jù)根元素的類型,他有不同的行為。
1.1 元素類型不相同
無(wú)論什么時(shí)候,當(dāng)根元素類型不同時(shí),React將會(huì)銷毀原先的樹并重新構(gòu)建新的樹。
當(dāng)銷毀原先的樹時(shí),之前的DOM節(jié)點(diǎn)將全部被銷毀。組件會(huì)執(zhí)行componentWillUnmount()。當(dāng)構(gòu)建新的樹時(shí),新DOM節(jié)點(diǎn)將會(huì)插入DOM中。組件將會(huì)執(zhí)行componentWillMount() 以及 componentDidMount() 。與之前舊的樹相關(guān)的 state 都會(huì)丟失。
1.2 DOM元素類型相同
當(dāng)比較兩個(gè)相同類型的 React DOM 元素時(shí),React 檢查它們的屬性(attributes),保留相同的底層 DOM 節(jié)點(diǎn),只更新反生改變的屬性(attributes)。
當(dāng)處理完當(dāng)前DOM節(jié)點(diǎn)后,React會(huì)遞歸處理子節(jié)點(diǎn)。
1.3 相同類型的組件
當(dāng)一個(gè)組件更新的時(shí)候,組件實(shí)例保持不變,以便在渲染中保持state。React會(huì)更新組件實(shí)例的屬性來(lái)匹配新的元素,并在元素實(shí)例上調(diào)用 componentWillReceiveProps() 和 componentWillUpdate()。
1.4 子元素遞歸
例如,當(dāng)給子元素末尾添加一個(gè)元素,在兩棵樹之間轉(zhuǎn)化中性能就不錯(cuò):
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
React 會(huì)比較兩個(gè) <li>first</li> 樹與兩個(gè) <li>second</li> 樹,然后插入 <li>third</li> 樹。
如果在開始處插入一個(gè)節(jié)點(diǎn)也是這樣簡(jiǎn)單地實(shí)現(xiàn),那么性能將會(huì)很差。例如,在下面兩棵樹的轉(zhuǎn)化中性能就不佳。
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
React 將會(huì)改變每一個(gè)子節(jié)點(diǎn)而沒有意識(shí)到需要保留 <li>Duke</li> 和 <li>Villanova</li> 兩個(gè)子樹。這種低效是一個(gè)問(wèn)題。
1.5 Keys
React提供了一個(gè)key屬性(attributes)。當(dāng)節(jié)點(diǎn)有了key,React使用key去比較原來(lái)的書的子節(jié)點(diǎn)和之后的節(jié)點(diǎn)。當(dāng)我們添加一個(gè)key時(shí),可以是上面抵消的例子轉(zhuǎn)換變高效:
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
此時(shí)React知道只有'2014'key的元素是新的,剩下的兩個(gè)元素僅僅只是被移動(dòng)而已。這樣就會(huì)保留剩下的兩個(gè)元素只更新新加入的元素。
1.6 總結(jié)
因?yàn)镽eact依賴這兩個(gè)啟發(fā)式,如果背后的假設(shè)沒有得到滿足,性能將會(huì)受到影響。
- 算法不會(huì)嘗試匹配不同節(jié)點(diǎn)類型的子樹。
- keys應(yīng)該是穩(wěn)定的,可預(yù)測(cè)的并且是唯一的。不穩(wěn)定的 key (類似于 Math.random() 函數(shù)的結(jié)果)可能會(huì)產(chǎn)生非常多的組件實(shí)例并且 DOM 節(jié)點(diǎn)也會(huì)非必要性的重新創(chuàng)建。這將會(huì)造成極大的性能損失和組件內(nèi)state的丟失