一、是什麼#
跟Vue
一致,React
通過引入Virtual DOM
的概念,極大地避免無效的Dom
操作,使我們的頁面的構建效率提到了極大的提升
而diff
算法就是更高效地通過對比新舊Virtual DOM
來找出真正的Dom
變化之處
傳統 diff 算法通過循環遞歸對節點進行依次對比,效率低下,算法複雜度達到 O (n^3),react
將算法進行一個優化,複雜度將為O(n)
,兩者效率差距如下圖:
二、原理#
react
中diff
算法主要遵循三個層級的策略:
-
tree 層級
-
conponent 層級
-
element 層級
tree 層級#
DOM
節點跨層級的操作不做優化,只會對相同層級的節點進行比較
只有刪除、創建操作,沒有移動操作,如下圖:
react
發現新樹中,R 節點下沒有了 A,那麼直接刪除 A,在 D 節點下創建 A 以及下屬節點
上述操作中,只有刪除和創建操作
conponent 層級#
如果是同一類的組件,則會繼續往下diff
運算,如果不是一個類的組件,那麼直接刪除這個組件下的所有子節點,創建新的
當component D
換成了component G
後,即使兩者的結構非常類似,也會將D
刪除再重新創建G
element 層級#
對於比較同一層級的節點們,每個節點在對應的層級用唯一的key
作為標識
提供了 3 種節點操作,分別為 INSERT_MARKUP
(插入)、MOVE_EXISTING
(移動) 和 REMOVE_NODE
(刪除)
如下場景:
通過key
可以準確地發現新舊集合中的節點都是相同的節點,因此無需進行節點刪除和創建,只需要將舊集合中節點的位置進行移動,更新為新集合中節點的位置
流程如下表:
- index: 新集合的遍歷下標。
- oldIndex:當前節點在老集合中的下標
- maxIndex:在新集合訪問過的節點中,其在老集合的最大下標
如果當前節點在新集合中的位置比老集合中的位置靠前的話,是不會影響後續節點操作的,這裡這時候被動字節不用動
操作過程中只比較 oldIndex 和 maxIndex,規則如下:
- 當 oldIndex>maxIndex 時,將 oldIndex 的值賦值給 maxIndex
- 當 oldIndex=maxIndex 時,不操作
- 當 oldIndex<maxIndex 時,將當前節點移動到 index 的位置
diff
過程如下:
- 節點 B:此時 maxIndex=0,oldIndex=1;滿足 maxIndex<oldIndex,因此 B 節點不動,此時 maxIndex= Math.max (oldIndex, maxIndex),就是 1
- 節點 A:此時 maxIndex=1,oldIndex=0;不滿足 maxIndex<oldIndex,因此 A 節點進行移動操作,此時 maxIndex= Math.max (oldIndex, maxIndex),還是 1
- 節點 D:此時 maxIndex=1, oldIndex=3;滿足 maxIndex<oldIndex,因此 D 節點不動,此時 maxIndex= Math.max (oldIndex, maxIndex),就是 3
- 節點 C:此時 maxIndex=3,oldIndex=2;不滿足 maxIndex< oldIndex,因此 C 節點進行移動操作,當前已經比較完了
當 ABCD 節點比較完成後,diff
過程還沒完,還會整體遍歷老集合中節點,看有沒有沒用到的節點,有的話,就刪除
三、注意事項#
對於簡單列表渲染而言,不使用key
比使用key
的性能,例如:
將一個 [1,2,3,4,5],渲染成如下的樣子:
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
後續更改成 [1,3,2,5,4],使用key
與不使用key
作用如下:
1.加key
<div key='1'>1</div> <div key='1'>1</div>
<div key='2'>2</div> <div key='3'>3</div>
<div key='3'>3</div> ========> <div key='2'>2</div>
<div key='4'>4</div> <div key='5'>5</div>
<div key='5'>5</div> <div key='4'>4</div>
操作:節點2移動至下標為2的位置,節點4移動至下標為4的位置。
2.不加key
<div>1</div> <div>1</div>
<div>2</div> <div>3</div>
<div>3</div> ========> <div>2</div>
<div>4</div> <div>5</div>
<div>5</div> <div>4</div>
操作:修改第1個到第5個節點的innerText
如果我們對這個集合進行增刪的操作改成 [1,3,2,5,6]
1.加key
<div key='1'>1</div> <div key='1'>1</div>
<div key='2'>2</div> <div key='3'>3</div>
<div key='3'>3</div> ========> <div key='2'>2</div>
<div key='4'>4</div> <div key='5'>5</div>
<div key='5'>5</div> <div key='6'>6</div>
操作:節點2移動至下標為2的位置,新增節點6至下標為4的位置,刪除節點4。
2.不加key
<div>1</div> <div>1</div>
<div>2</div> <div>3</div>
<div>3</div> ========> <div>2</div>
<div>4</div> <div>5</div>
<div>5</div> <div>6</div>
操作:修改第1個到第5個節點的innerText
由於dom
節點的移動操作開銷是比較昂貴的,沒有key
的情況下要比有key
的性能更好。