React diff
- 為什麼使用虛擬 DOM ?
瀏覽器在處理 DOM 的時候會很慢,處理 JavaScript 會很快,頁面複雜的時候,頻繁操作 DOM 會有很大的效能開銷(每次資料變化都會引起整個 DOM 樹的重繪和重排)。
為了避免頻繁操作 DOM,React 會維護兩個虛擬 DOM,如果有資料更新,會藉此計算出所有修改的狀態集中到一起,統一更新一次虛擬 DOM。- diff 是什麼?
React 會維護兩個虛擬 DOM,如果有資料更新,會藉此計算出所有修改的狀態,然後將這些變化更新到真實 DOM 上,diff 演算法就是比較兩個虛擬 DOM 樹的策略。- 虛擬 DOM 一定會提高效能嗎?
不一定。
因為虛擬 DOM 雖然會減少 DOM 操作,但也無法避免 DOM 操作。
它的優勢是在於 diff 演算法和批次處理策略,將所有的 DOM 操作蒐集起來,一次性去改變真實的 DOM,但在首次渲染上,虛擬 DOM 會多了一層計算,消耗一些效能,所以有可能會比 html 渲染的要慢
傳統 diff 演算法
透過迴圈遞迴對節點進行依此對比,其演算法複雜度達到了O(n^ 3),也就是說,如果展示一千個節點,就要計算十億次。
React v16 最佳化
React透過三大策略完成了最佳化:
Web UI 中 DOM 節點跨層級的移動操作特別少,可以忽略不計。
擁有相同類的兩個元件將會生成相似的樹形結構,擁有不同類的兩個元件將會生成不同的樹形結構。
對於同一層級的一組子節點,它們可以透過唯一 id 進行區分。
分別對應:tree diff、component diff、element diff
tree diff
同級比較:在對比的過程中,如果發現節點不在了,會完全刪除不會對其他地方進行比較,這樣只需要對樹遍歷一次就OK了
component diff
兩種策略:
- 相同型別的元件
按照層級比較繼續比較虛擬DOM樹即可。
特殊情況:當元件A如果變化為元件B的時候,有可能虛擬DOM並沒有任何變化,所以使用者可以透過shouldComponentUpdate() 來判斷是否需要更新,判斷是否計算
- 不同型別的元件
React會直接判定該元件為dirty component(髒元件),無論結構是否相似,只要判斷為髒元件就會直接替換整個元件的所有節點
element diff
節點比較,對於同一層級的一子自節點,透過唯一的key進行比較
當所有節點處以同一層級時,React 提供了三種節點操作:
- 插入(INSERT_MARKUP):新的 component 型別不在老集合裡, 即是全新的節點,需要對新節點執行插入操作。
- 移動(MOVE_EXISTING):在老集合有新 component 型別,且 element 是可更新的型別,generateComponentChildren 已呼叫 receiveComponent,這種情況下prevChild=nextChild,就需要做移動操作,可以複用以前的 DOM 節點。(傳統 diff 檢測到不相同就直接刪除重建)
- 刪除(REMOVE_NODE):老 component 型別,在新集合裡也有,但對應的 element 不同則不能直接複用和更新,需要執行刪除操作,或者老 component 不在新集合裡的,也需要執行刪除操作。
具體的 diff 過程
遍歷新的一層節點,使用 lastIndex 記錄每次比較後的節點最新索引,使用 index 表示在舊的一層中該節點的索引。
在新的一層中,如果發現該節點存在過,透過 key 找到該節點在舊的一層中的座標 index,如果 index < lastIndex 就移動該節點,否則不動,然後更新 lastIndex = max(lastIndex, index)
如果該節點沒有存在過,就新建,但是不更新 lastIndex
相同位置
插入新節點
當最後一個節點移動到第一個位置時,可能出現的問題(所有節點都需要移動)
為什麼不能使用 index 作為 key 值?
因為透過 key 找到的兩個節點不相同,需要刪除重建,使用唯一值(比如id)描述每個節點,這樣起到 diff 演算法的作用。
下面的例子中的節點全都需要重建,因為每次透過 key 找到的節點都不相同,無法發揮 diff 演算法的作用
參考
https://juejin.cn/post/7116326409961734152