淺談React的diff算法和key屬性

React快速的致勝法寶是虛擬DOM及其高效的diff算法。

可以無需擔心性能問題而“隨時”刷新整個頁面,虛擬DOM可以確保只對界面上真正變化的部分進行實際的DOM操作。雖然在實際開發中,基本無需關心虛擬DOM是如何運作的,但理解其運行機制不僅能幫助更好地理解React組件的生命週期,還會對進一步優化React程序也會有很大幫助。

 

1、Diff算法

Web界面由DOM樹構成,頁面某部分發生變化,其實是某個DOM節點發生了變化。變化前後對應兩套界面,需要React比較兩個界面的區別,這就需要通過diff算法對DOM樹進行分析,即針對變化前後的兩棵DOM樹,找到最少的轉換步驟。

 

標準的diff算法的複雜度爲O(n^3),Facebook工程師結合Web界面的特點做出了以下兩個簡單的假設,使得Diff算法的複雜度直接降低到O(n):

① 相同的組件產生類似的DOM結構,不同的組件產生不同的DOM結構;

② 對於同一層次的一組子節點,可以通過唯一的id進行區分。

 

逐層進行節點比較:

在React中,兩棵DOM樹只會對同一層的節點進行比較。若發現節點已不存在,則該節點及其子節點會被完全刪除,不會用於進一步的比較。這樣,只需要對樹進行一次遍歷,就能完成整個DOM樹的比較。

對於同層節點,若節點本身完全相同(類型相同,屬性相同),只是位置不同,則React只需要考慮同層節點的位置變換,不需要進行節點的銷燬和重新創建,這就需要用到下面介紹的key屬性,但對於不同層的節點,只能銷燬和重新創建。

 

比較兩個虛擬DOM節點,可以分爲以下三種情況:

1) 節點類型不同; 

當在樹中的同一位置前後的節點類型不同,React會直接刪除原節點,然後創建並插入新的節點。

注意:刪除節點即徹底銷燬該節點,也就是說,後續不會查找是否有另外一個節點等同於刪除的該節點。如果刪除的該節點有子節點,那麼子節點也會被刪除。這也是diff算法複雜度能降到O(n)的原因。

同理,當樹的同一個位置遇到前後不同的組件時,也是銷燬原組件,把新的組件加上去。這應用了第一個假設,不同的組件一般會產生不同的DOM結構,與其浪費時間去比較不同的DOM結構,還不如完全創建一個新的組件加上去。

2) 節點類型相同,但是屬性不同。

React會對屬性進行重設從而實現節點的轉換。

3) 節點類型相同且屬性相同。

對於同層節點,若節點本身完全相同(類型相同且屬性相同),只是位置不同,則React只需要考慮同層節點的位置變換,不需要進行節點的銷燬和重新創建,這就需要用到下面介紹的key屬性。

對於不同層的節點,即使節點本身完全相同(類型相同且屬性相同),也只能銷燬和重新創建。

 

2、key屬性

爲列表節點提供唯一的key屬性,可以幫助React定位到正確的節點進行比較,從而大幅減少DOM操作的次數,提高性能。

React在處理列表時如果未給每個元素設置key,會提示如下找不到key的警告:

Warning: Each child in an array or iterator should have a unique "key" prop.

雖然無視該警告大部分界面也能正確工作,但會帶來潛在的性能問題——React可能無法高效地更新該列表。

eg1:

某列表爲a-b-c-d,若需要往b和c直接插入節點e,在jQuery中使用$(b).after(e)即可實現,而在React中只會告訴React新的列表應該是a-b-e-c-d,更新界面交由diff算法完成。如果每個節點都沒有唯一的標識key,那麼React將無法識別每一個節點,導致更新過程很低效,即將c更新成e,d更新成c,最後再插入一個d節點;而如果給每個節點唯一的標識key,React將能夠找到正確的位置去插入新的節點。

eg2:

某虛擬DOM樹的某一層原有兩個節點a和b,頁面更新後得到的新DOM樹的該層還是隻有a和b,只是a和b的位置與原來不同了。如果未提供唯一的標識key,React將認爲a和b對應的位置前後的組件類型不同,而選擇完全銷燬後重新創建;而如果提供了唯一的標識key,React將能夠判斷出a和b只是位置不同,更新位置即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章