React 面試常見觀念系列 - Virtual DOM, Reconciliation

React 面試常見觀念系列 - Virtual DOM, Reconciliation

在 React 中,什麼是 Virtual DOM?

在網頁世界中,DOM 是一個用樹狀結構表達網頁文件的模型
也是我們可以在瀏覽器中開發者工具中可以看到的實際結構
例如:這樣的網頁

我們用 HTML 來表達 DOM 可以等同於

<div id="header">
  <h1 class="title">React</h1>
  <p class="content">A JavaScript library for building user</p>
</div>

而虛擬 DOM 則是,用 JavaScript 物件的形式,來儲存這樣的結構(實際上更加複雜)

{
    tag: "div",
    props: {
        id: "header"
    },
    children: [
        {
            tag: "h1",
            props: { className: "title" },
            children: ["React"]
        },
        {
            tag: "p",
            props: { className: "content" },
            children: ["A JavaScript library for building user"]
        }
    ]
}

那麼為什麼要使用 Virtual DOM 呢?

1. 操作成本與效能
操作真實 DOM 的成本可能是巨大的
即使畫面只有一小部分需要更新,卻需要重新繪製一整個 DOM
就如同在麥當勞點了全餐,但是你發現店員把可樂給成了紅茶
店員說「不好意思,我們馬上為您把餐點重做」
結果馬上轉身把漢堡跟薯條也都丟掉了
你一定會心想「WTF ? 不是重新給一杯飲料就好嗎?」

尤其在現代網頁中,網頁往往是結構龐大且畫面更新頻繁
勢必會有效能低下的問題
所以當畫面更新時,React 會建立新的 Virtual DOM tree 跟舊的 tree 進行比較
只更新有改變的部分。

2. 擴展性

  • 由於 Virtual DOM 其實就是 JS 物件,可以用 JS 的方法來操作 DOM
  • Virtual DOM 不只可以轉化成網頁,也可以轉化成 Android 或是 iOS 的介面
    所以對開發者角度來說是更有跨平台擴展性的,例如: React Native
  • 不直接操作 DOM,可以降低被 XSS 攻擊的風險

那麼它是怎麼更新 Virtual DOM tree 的呢?

如果直接比較新舊兩棵樹,那麼就必須一個一個節點走訪比對,會消耗大量效能
所以 React 使用 Diffing 演算法來找出差異,進行更新(替換、刪除、移動、修改)
邏輯如下:

1. 檢查節點類型

  • 如果新舊類型不同 => 直接放棄原有的子樹,直接重建,省去後面走訪與比較所浪費的效能
  • 如果新舊類型相同 => 保留原有子樹,只更新有差異的屬性

例如: 把一開始的<div>元素改成<section>或其他元素
那麼 React 會卸載原本的<div>以及裡面的子樹
包含<h1><p>都會被銷毀並重建

// 原本的
<div id="header">
  <h1>...</h1> 
  <p class="content">A JavaScript library for building user</p>
</div>

// 新的
<section id="header">
  <h1>...</h1> 
  <p class="content">A JavaScript library for building user</p>
</section>

如果是同類型,則只會比對、更新有改變的屬性

// 原本的
<div id="header">
  <h1>...</h1> 
  <p class="content">...</p>
</div>

// 新的
<div id="header">
  <h1>...</h1> 
  <p class="newContent">...</p>
</div>

2. 在遍歷 DOM 節點的子元素時

  • 沒有 key 屬性 => React 可能會分不出來到底是部分修改還是全部更新,而造成效能浪費
  • 擁有 key 屬性 => React 會用 key 來作為識別,而不會直接全部更新

key 的部分可以獨立出一篇,就不深入解釋

所以 Virtual DOM 就是無敵的了?

Diffing 算法也是需要成本的!
當遍歷樹節點、並且一一比較的過程,過於複雜
那麼就有可能出現,不進行比較,直接重新建立DOM還比較划算的情況
所以在設計上,官方也提醒我們

  1. 如果頁面常常在兩種不同類型的元件或元素間切換,但其實內容是差不多的,那建議可以改成同類型的
  2. 如果是大型的列表,要設計合理、穩定的key,才能把效能最佳化

更多資料可以參考官方文件
https://reactjs.org/docs/reconciliation.html#the-diffing-algorithm