React快速的致勝法寶是虛擬DOM及其高效的diff算法。
可以無需擔(dān)心性能問題而“隨時”刷新整個頁面,虛擬DOM可以確保只對界面上真正變化的部分進行實際的DOM操作。雖然在實際開發(fā)中,基本無需關(guān)心虛擬DOM是如何運作的,但理解其運行機制不僅能幫助更好地理解React組件的生命周期,還會對進一步優(yōu)化React程序也會有很大幫助。
1、Diff算法
Web界面由DOM樹構(gòu)成,頁面某部分發(fā)生變化,其實是某個DOM節(jié)點發(fā)生了變化。變化前后對應(yīng)兩套界面,需要React比較兩個界面的區(qū)別,這就需要通過diff算法對DOM樹進行分析,即針對變化前后的兩棵DOM樹,找到最少的轉(zhuǎn)換步驟。
標(biāo)準的diff算法的復(fù)雜度為O(n^3),F(xiàn)acebook工程師結(jié)合Web界面的特點做出了以下兩個簡單的假設(shè),使得Diff算法的復(fù)雜度直接降低到O(n):
① 相同的組件產(chǎn)生類似的DOM結(jié)構(gòu),不同的組件產(chǎn)生不同的DOM結(jié)構(gòu);
② 對于同一層次的一組子節(jié)點,可以通過唯一的id進行區(qū)分。
逐層進行節(jié)點比較:
在React中,兩棵DOM樹只會對同一層的節(jié)點進行比較。若發(fā)現(xiàn)節(jié)點已不存在,則該節(jié)點及其子節(jié)點會被完全刪除,不會用于進一步的比較。這樣,只需要對樹進行一次遍歷,就能完成整個DOM樹的比較。
對于同層節(jié)點,若節(jié)點本身完全相同(類型相同,屬性相同),只是位置不同,則React只需要考慮同層節(jié)點的位置變換,不需要進行節(jié)點的銷毀和重新創(chuàng)建,這就需要用到下面介紹的key屬性,但對于不同層的節(jié)點,只能銷毀和重新創(chuàng)建。
比較兩個虛擬DOM節(jié)點,可以分為以下三種情況:
1)?節(jié)點類型不同;?
當(dāng)在樹中的同一位置前后的節(jié)點類型不同,React會直接刪除原節(jié)點,然后創(chuàng)建并插入新的節(jié)點。
注意:刪除節(jié)點即徹底銷毀該節(jié)點,也就是說,后續(xù)不會查找是否有另外一個節(jié)點等同于刪除的該節(jié)點。如果刪除的該節(jié)點有子節(jié)點,那么子節(jié)點也會被刪除。這也是diff算法復(fù)雜度能降到O(n)的原因。
同理,當(dāng)樹的同一個位置遇到前后不同的組件時,也是銷毀原組件,把新的組件加上去。這應(yīng)用了第一個假設(shè),不同的組件一般會產(chǎn)生不同的DOM結(jié)構(gòu),與其浪費時間去比較不同的DOM結(jié)構(gòu),還不如完全創(chuàng)建一個新的組件加上去。
2)?節(jié)點類型相同,但是屬性不同。
React會對屬性進行重設(shè)從而實現(xiàn)節(jié)點的轉(zhuǎn)換。
3)?節(jié)點類型相同且屬性相同。
對于同層節(jié)點,若節(jié)點本身完全相同(類型相同且屬性相同),只是位置不同,則React只需要考慮同層節(jié)點的位置變換,不需要進行節(jié)點的銷毀和重新創(chuàng)建,這就需要用到下面介紹的key屬性。
對于不同層的節(jié)點,即使節(jié)點本身完全相同(類型相同且屬性相同),也只能銷毀和重新創(chuàng)建。
2、key屬性
為列表節(jié)點提供唯一的key屬性,可以幫助React定位到正確的節(jié)點進行比較,從而大幅減少DOM操作的次數(shù),提高性能。
React在處理列表時如果未給每個元素設(shè)置key,會提示如下找不到key的警告:
Warning: Each?child?in?an?array?or iterator should have a unique "key" prop.
雖然無視該警告大部分界面也能正確工作,但會帶來潛在的性能問題——React可能無法高效地更新該列表。
eg1:
某列表為a-b-c-d,若需要往b和c直接插入節(jié)點e,在jQuery中使用$(b).after(e)即可實現(xiàn),而在React中只會告訴React新的列表應(yīng)該是a-b-e-c-d,更新界面交由diff算法完成。如果每個節(jié)點都沒有唯一的標(biāo)識key,那么React將無法識別每一個節(jié)點,導(dǎo)致更新過程很低效,即將c更新成e,d更新成c,最后再插入一個d節(jié)點;而如果給每個節(jié)點唯一的標(biāo)識key,React將能夠找到正確的位置去插入新的節(jié)點。
eg2:
某虛擬DOM樹的某一層原有兩個節(jié)點a和b,頁面更新后得到的新DOM樹的該層還是只有a和b,只是a和b的位置與原來不同了。如果未提供唯一的標(biāo)識key,React將認為a和b對應(yīng)的位置前后的組件類型不同,而選擇完全銷毀后重新創(chuàng)建;而如果提供了唯一的標(biāo)識key,React將能夠判斷出a和b只是位置不同,更新位置即可。