日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網(wǎng)站首頁 編程語言 正文

React之虛擬DOM的實現(xiàn)原理_React

作者:huangpb0624 ? 更新時間: 2023-03-13 編程語言

React虛擬DOM機制

  • 虛擬DOM本質上是JavaScript對象,是對真實DOM的抽象
  • 狀態(tài)變更時,記錄新樹和舊樹的差異
  • 最后把差異更新到真正的dom中

React引入了虛擬DOM(Virtual DOM)的機制:在瀏覽器端用Javascript實現(xiàn)了一套DOM API。

基于React進行開發(fā)時所有的DOM構造都是通過虛擬DOM進行,每當數(shù)據(jù)變化時,React都會重新構建整個DOM樹,然后React將當前整個DOM樹和上一次的DOM樹進行對比,得到DOM結構的區(qū)別,然后僅僅將需要變化的部分進行實際的瀏覽器DOM更新。

而且React能夠批量處理虛擬DOM的刷新,在一個事件循環(huán)(Event Loop)內的兩次數(shù)據(jù)變化會被合并,例如你連續(xù)的先將節(jié)點內容從A變成B,然后又從B變成A,React會認為UI不發(fā)生任何變化。

盡管每一次都需要構造完整的虛擬DOM樹,但是因為虛擬DOM是內存數(shù)據(jù),性能是極高的,而對實際DOM進行操作的僅僅是Diff部分,因而能達到提高性能的目的。這樣,在保證性能的同時,開發(fā)者將不再需要關注某個數(shù)據(jù)的變化如何更新到一個或多個具體的DOM元素,而只需要關心在任意一個數(shù)據(jù)狀態(tài)下,整個界面是如何Render的。

總之一句話:根據(jù) React 的設計,所有的 DOM 變動,都先在虛擬 DOM 上發(fā)生,然后再將實際發(fā)生變動的部分,反映在真實 DOM上

React diff 算法

diff 算法作為Virtual DOM的加速器,其算法的改進優(yōu)化是React整個界面渲染的基礎和性能的保障,同時也是React源碼中最神秘的,最不可思議的部分。

1. 傳統(tǒng) diff 算法

計算一棵樹形結構轉換為另一棵樹形結構需要最少步驟,如果使用傳統(tǒng)的diff算法通過循環(huán)遞歸遍歷節(jié)點進行對比,其復雜度要達到O(n^3),其中n是節(jié)點總數(shù),效率十分低下,假設我們要展示1000個節(jié)點,那么我們就要依次執(zhí)行上十億次的比較。

下面附上一則簡單的傳統(tǒng)diff算法:

let result = [];
// 比較葉子節(jié)點
const diffLeafs = function (beforeLeaf, afterLeaf) {
    // 獲取較大節(jié)點樹的長度
    let count = Math.max(beforeLeaf.children.length, afterLeaf.children.length);
    // 循環(huán)遍歷
    for (let i = 0; i < count; i++) {
        const beforeTag = beforeLeaf.children[i];
        const afterTag = afterLeaf.children[i];
        // 添加 afterTag 節(jié)點
        if (beforeTag === undefined) {
            result.push({ type: "add", element: afterTag });
            // 刪除 beforeTag 節(jié)點
        } else if (afterTag === undefined) {
            result.push({ type: "remove", element: beforeTag });
            // 節(jié)點名改變時,刪除 beforeTag 節(jié)點,添加 afterTag 節(jié)點
        } else if (beforeTag.tagName !== afterTag.tagName) {
            result.push({ type: "remove", element: beforeTag });
            result.push({ type: "add", element: afterTag });
            // 節(jié)點不變而內容改變時,改變節(jié)點
        } 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;
}

2. react diff 算法

1. diff 策略

下面介紹一下react diff算法的3個策略

  • Web UI 中DOM節(jié)點跨層級的移動操作特別少,可以忽略不計
  • 擁有相同類的兩個組件將會生成相似的樹形結構,擁有不同類的兩個組件將會生成不同的樹形結構。
  • 對于同一層級的一組子節(jié)點,它們可以通過唯一id進行區(qū)分。

對于以上三個策略,react 分別對 tree diff, component diff, element diff 進行算法優(yōu)化。

2. tree diff

基于策略一,WebUI中DOM節(jié)點跨層級的移動操作少的可以忽略不計,React對Virtual DOM樹進行層級控制,只會對相同層級的DOM節(jié)點進行比較,即同一個父元素下的所有子節(jié)點,當發(fā)現(xiàn)節(jié)點已經(jīng)不存在了,則會刪除掉該節(jié)點下所有的子節(jié)點,不會再進行比較。這樣只需要對DOM樹進行一次遍歷,就可以完成整個樹的比較。復雜度變?yōu)镺(n);

疑問:當我們的DOM節(jié)點進行跨層級操作時,diff 會有怎么樣的表現(xiàn)呢?

如下圖所示,A節(jié)點及其子節(jié)點被整個移動到D節(jié)點下面去,由于React只會簡單的考慮同級節(jié)點的位置變換,而對于不同層級的節(jié)點,只有創(chuàng)建和刪除操作,所以當根節(jié)點發(fā)現(xiàn)A節(jié)點消失了,就會刪除A節(jié)點及其子節(jié)點,當D發(fā)現(xiàn)多了一個子節(jié)點A,就會創(chuàng)建新的A作為其子節(jié)點。?

此時,diff的執(zhí)行情況是:

createA-->createB-->createC-->deleteA

由此可以發(fā)現(xiàn),當出現(xiàn)節(jié)點跨層級移動時,并不會出現(xiàn)想象中的移動操作,而是會進行刪除,重新創(chuàng)建的動作,這是一種很影響React性能的操作。因此官方也不建議進行DOM節(jié)點跨層級的操作。

3. component diff

  • React是基于組件構建應用的,對于組件間的比較所采用的策略也是非常簡潔和高效的。
  • 如果是同一個類型的組件,則按照原策略進行Virtual DOM比較。
  • 如果不是同一類型的組件,則將其判斷為dirty component,從而替換整個組價下的所有子節(jié)點。
  • 如果是同一個類型的組件,有可能經(jīng)過一輪Virtual DOM比較下來,并沒有發(fā)生變化。如果我們能夠提前確切知道這一點,那么就可以省下大量的diff運算時間。因此,React允許用戶通過shouldComponentUpdate()來判斷該組件是否需要進行diff算法分析。

如下圖所示,當組件D變?yōu)榻M件G時,即使這兩個組件結構相似,一旦React判斷D和G是不用類型的組件,就不會比較兩者的結構,而是直接刪除組件D,重新創(chuàng)建組件G及其子節(jié)點。

雖然當兩個組件是不同類型但結構相似時,進行diff算法分析會影響性能,但是畢竟不同類型的組件存在相似DOM樹的情況在實際開發(fā)過程中很少出現(xiàn),因此這種極端因素很難在實際開發(fā)過程中造成重大影響。?

4. element diff

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

  • INSERT_MARKUP:新的組件類型不在舊集合中,即全新的節(jié)點,需要對新節(jié)點進行插入操作。
  • MOVE_EXISTING:舊集合中有新組件類型,且element是可更新的類型,這時候就需要做移動操作,可以復用以前的DOM節(jié)點。
  • REMOVE_NODE:舊組件類型,在新集合里也有,但對應的element不同則不能直接復用和更新,需要執(zhí)行刪除操作,或者舊組件不在新集合里的,也需要執(zhí)行刪除操作。?

總結

通過diff策略,將算法從O(n^3)簡化為O(n)。

分層求異,對tree diff進行優(yōu)化。

分組件求異,相同類生成相似樹形結構、不同類生成不同樹形結構,對component diff進行優(yōu)化。

設置key,對element diff進行優(yōu)化。

盡量保持穩(wěn)定的DOM結構、避免將最后一個節(jié)點移動到列表首部、避免節(jié)點數(shù)量過大或更新過于頻繁。

最后

原文鏈接:https://blog.csdn.net/huangpb123/article/details/84947475

欄目分類
最近更新