React底層原理解析之diff算法

React的diff算法是在哪裏進行計算的?

diff算法是在render裏面進行計算的。

React的diff算法與傳統的diff算法的區別:

傳統的diff算法:
計算一棵樹形結構轉換爲另一顆樹形結構需要最少步驟,如果使用傳統的diff算法通過循環遞歸遍歷節點進行對比,其複雜度要達到O(n^3),其中n是節點總數,效率十分低下,假設我們要展示1000個節點,那麼我們就要依次執行上十億次的比較。下面是diff算法源碼:

let result = [];
// 比較葉子節點
const diffLeafs = function (beforeLeaf, afterLeaf) {
    // 獲取較大節點樹的長度
    let count = Math.max(beforeLeaf.children.length, afterLeaf.children.length);
    // 循環遍歷
    for (let i = 0; i < count; i++) {
        const beforeTag = beforeLeaf.children[i];
        const afterTag = afterLeaf.children[i];
        // 添加 afterTag 節點
        if (beforeTag === undefined) {
            result.push({ type: "add", element: afterTag });
            // 刪除 beforeTag 節點
        } else if (afterTag === undefined) {
            result.push({ type: "remove", element: beforeTag });
            // 節點名改變時,刪除 beforeTag 節點,添加 afterTag 節點
        } else if (beforeTag.tagName !== afterTag.tagName) {
            result.push({ type: "remove", element: beforeTag });
            result.push({ type: "add", element: afterTag });
            // 節點不變而內容改變時,改變節點
        } else if (beforeTag.innerHTML !== afterTag.innerHTML) {
            if (beforeTag.children.length === 0) {
                result.push({
                    type: "changed",
                    beforeElement: beforeTag,
                    afterElement: afterTag,
                    html: afterTag.innerHTML
                });
            } else {
                // 遞歸比較
                diffLeafs(beforeTag, afterTag);
            }
        }
    }
    return result;
}

react的diff算法

1.diff策略
下面介紹react diff算法的三個策略
(1)Web UI中DOM節點跨層級的移動操作特別少,可以忽略不計
(2)擁有相同類的倆個組件將會生成相似的樹形結構,擁有不同類的倆個組件將會生成不同的樹形結構。
(3)對於同一層級的一組子節點,它們可以通過唯一id進行區分。

對於以上三個策略,react分別對tree diff,component diff,element diff進行算法優化。
2.diff粒度

tree diff

基於策略一,WebUI中DOM節點跨層級的移動操作少的可以忽略不計,React對Virtual DOM樹進行層級控制,只會對相同層級的DOM節點進行比較,即同一個父元素下的所有子節點,當發現節點已經不存在了,則會刪除掉該節點下所有的子節點,不會再進行比較。這樣只需要對DOM樹進行一次遍歷,就可以完成整個樹的比較。複雜度變爲O(n);
疑問:當我們的DOM節點進行跨層級操作時,diff會有怎麼樣的表現呢?
當出現節點跨層級移動時,並不會出現想象中的移動操作,而是會進行刪除,重新創建的動作,這是一種很影響React性能的操作。因此官方也不建議進行DOM節點跨層級的操作。

component diff

react是基於組件構建應用的,對於組件間的比較所採用的策略也是非常簡潔和高效的。
如果是同一類型的組件,則按照原策略進行Virtual DOM比較
如果不是同一類型的組件,則將其判斷爲dirty component,從而替換整個組件下的所有子節點。
如果是同一個類型的組件,有可能經過一輪Virtual DOM比較下拉,並沒有發生變化。如果我們能夠提前確切知道這一點,那麼就可以省下大量的diff運算時間。因此,React允許用戶通過shouldComponentUpdate()來判斷該組件是否需要進行diff算法分析。
比如說當組件D變爲組件G時,即使這兩個組件結構相似,一旦React判斷D和G是不用類型的組件,就不會比較兩者的結構,而是直接刪除組件D,重新創建組件G及其子節點。雖然當兩個組件是不同類型但結構相似時,進行diff算法分析會影響性能,但是畢竟不同類型的組件存在相似DOM樹的情況在實際開發過程中很少出現,因此這種極端因素很難在實際開發過程中造成重大影響。

element diff
當節點屬於同一層級時,diff提供了3種節點操作,分別爲INSERT_MARKUP(插入),MOVE_EXISTING(移動),REMOVE_NODE(刪除)。

INSERT_MARKUP:新的組件類型不在舊集合中,即全新的節點,需要對新節點進行插入操作。

MOVE_EXISTING:舊集合中有新組件類型,且element是可更新的類型,這時候就需要做移動操作,可以複用以前的DOM節點。

REMOVE_NODE:舊組件類型,在新集合裏也有,但對應的element不同則不能直接複用和更新,需要執行刪除操作,或者舊組件不在新集合裏的,也需要執行刪除操作。

總結:

  1. React通過制定大膽的diff策略,將 O(n3) 複雜度的問題轉換成 O(n) 複雜度的問題;
    React是如何將O(n3) 複雜度的問題轉換成 O(n) 的?
    1. 只進行同級比較
    2. 不同類的React組件會被當做完全不同的DOM結構而被完全替換
    3. key prop:開發人員可以通過給Virtual DOM一個唯一的key屬性暗示React這是同一個DOM結構,反之若key值不同則會被當做完全不同的DOM結構。
  2. React通過分層求異的策略,對tree diff進行算法優化;
  3. React通過相同類生成相似樹形結構,不同類生成不同樹形結構的策略,對component diff進行算法優化。
  4. React通過設置唯一key的策略,對element diff進行算法優化;
  5. 建議,在開發組件時,保持穩定的DOM結構會有助於性能的提升;
  6. 建議,在開發過程中,儘量減少類似將最後一個節點移動到列表首部的操作,當節點數量過大或更新操作過於頻繁時,在一定程度上會影響React的渲染性能。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章