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還比較划算的情況
所以在設計上,官方也提醒我們
- 如果頁面常常在兩種不同類型的元件或元素間切換,但其實內容是差不多的,那建議可以改成同類型的
- 如果是大型的列表,要設計合理、穩定的key,才能把效能最佳化
更多資料可以參考官方文件
https://reactjs.org/docs/reconciliation.html#the-diffing-algorithm