本文 AI 產出,尚未審核
Vue 3 基礎概念:Virtual DOM 與響應式系統
簡介
在 Vue 3 中,Virtual DOM(虛擬 DOM)與 響應式系統 是框架能夠高效更新 UI 的兩大核心機制。Virtual DOM 讓我們可以在記憶體中先「模擬」一次 DOM 樹的變化,最後一次性比對差異(diff)再寫入真實的瀏覽器 DOM,從而大幅降低不必要的重排與重繪。
而 Vue 3 的響應式系統(基於 Proxy)則負責追蹤資料的變化,當資料變動時自動觸發相對應的 Virtual DOM 重新渲染。了解這兩者如何協同工作,才能寫出效能佳且易維護的 Vue 應用程式,尤其在大型專案或高互動介面中更顯重要。
核心概念
1. Virtual DOM 的運作原理
- 渲染階段:
render()函式根據當前的狀態(state)產生一棵虛擬節點樹(VNode tree)。 - 比對階段:Vue 會將新舊兩棵 VNode 樹做 diff,找出最小的變更集合。
- 補丁階段:根據 diff 結果,以最少的 DOM 操作更新真實的瀏覽器 DOM。
重點:只要資料沒有變化,Vue 會直接跳過 diff 與 patch,保持 UI 的穩定性。
2. Vue 3 的 Proxy‑based 響應式系統
| Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
|---|---|
| 只能偵測已有的屬性 | 可偵測新增/刪除屬性 |
需要 Vue.set 才能新增屬性 |
直接使用普通的 JavaScript 操作 |
| 代理深層結構較複雜 | 自動遞迴代理,支援嵌套物件 |
Vue 3 透過 reactive()、ref()、computed() 等 API 建立 依賴追蹤(dependency tracking)。當一個響應式值被讀取時,對應的「副作用函式」會被加入依賴集合;當值改變時,這些副作用函式會重新執行,觸發 Virtual DOM 的重新渲染。
3. reactive 與 ref 的差異
| API | 用途 | 取得值方式 |
|---|---|---|
reactive(obj) |
包裝物件或陣列,返回 Proxy | 直接讀寫屬性 (state.count) |
ref(value) |
包裝原始值或單一值,返回包含 .value 的物件 |
讀寫 .value (count.value) |
實務建議:若僅需要單一值或簡單類型,使用
ref;若是複雜結構則使用reactive。
4. computed:快取的衍生狀態
computed 會在其依賴的響應式資料變更時才重新計算,且結果會被 快取(cached),避免不必要的重算。
import { ref, computed } from 'vue'
const price = ref(1200)
const taxRate = ref(0.1)
// 計算稅後價格,只有 price 或 taxRate 變動時才會重新計算
const total = computed(() => price.value * (1 + taxRate.value))
console.log(total.value) // 1320
price.value = 1500
console.log(total.value) // 1650
5. 生命週期與渲染流程的結合
在 setup() 中建立的響應式資料會在掛載(mount)階段自動參與 Virtual DOM 的渲染。以下範例展示從 setup → render → patch 的完整流程。
import { defineComponent, reactive, onMounted } from 'vue'
export default defineComponent({
name: 'Counter',
setup() {
const state = reactive({ count: 0 })
// 模擬非同步資料取得
onMounted(() => {
setTimeout(() => {
state.count = 10 // 觸發依賴追蹤 → diff → patch
}, 1000)
})
return { state }
},
// 渲染函式(也可以使用 template)
render() {
// 每次 state.count 改變,這裡的 VNode 會重新產生
return h('div', `目前計數:${this.state.count}`)
}
})
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 直接修改 Prop | 直接在子組件中改變 props 會失去響應式追蹤,且在開發模式會觸發警告。 |
使用 emit('update:prop') 或在子組件內部建立 localCopy = ref(props.foo) 再操作。 |
| 非同步更新導致 UI 不同步 | 直接修改 ref 後立刻讀取可能得到舊值,因為更新是 微任務(micro‑task)排程。 |
透過 await nextTick() 或 watchEffect 觀察變化。 |
過度使用 reactive 包裝大型物件 |
大型物件每層都會被 Proxy 包裝,可能造成記憶體與效能負擔。 | 只對需要追蹤的屬性使用 reactive,其餘保持普通物件或使用 shallowReactive。 |
在 computed 中寫副作用 |
computed 應該是純函式,若包含副作用會導致不可預期的重渲染。 |
把副作用搬到 watch 或 watchEffect。 |
忘記在 setup 中返回需要的變數 |
未返回的變數不會被模板或渲染函式存取,導致 UI 不更新。 | 確認 setup 的返回物件包含所有要在模板中使用的屬性。 |
最佳實踐小結
- 盡量使用
ref包裝原始值,降低 Proxy 的深層代理成本。 - 利用
computed做衍生狀態,避免在模板中寫複雜邏輯。 - 在需要追蹤陣列或物件的變化時,使用
reactive,但配合toRefs或storeToRefs方便解構。 - 在大型列表渲染 時,使用
key讓 Virtual DOM 能正確對應舊節點,減少不必要的重排。 - 適時使用
nextTick取得最新的 DOM 狀態,避免因微任務延遲而產生錯誤。
實際應用場景
| 場景 | 為何需要 Virtual DOM & 響應式系統 |
|---|---|
| 即時儀表板(大量圖表、即時數據) | 每秒鐘的資料更新只會觸發變動的圖表節點,減少整頁重繪。 |
| 表單驗證(動態顯示錯誤訊息) | 透過 ref 追蹤每個欄位的錯誤狀態,僅在錯誤改變時更新相應的提示 UI。 |
| 無限滾動清單 | 使用 key 搭配 reactive 陣列,新增項目時 Virtual DOM 只在底部插入新節點。 |
| 多語系切換 | 透過 reactive 的語言設定,切換時只重新渲染文字節點,避免整頁閃爍。 |
| 動畫過渡(enter/leave) | Vue 內建的 <Transition> 依賴 Virtual DOM 判斷元素進出,確保動畫流暢。 |
總結
- Virtual DOM 為 Vue 提供了「先在記憶體中比對、再一次性更新」的高效渲染策略,使得即使在資料頻繁變動的情況下,也能保持流暢的使用者體驗。
- Vue 3 的 Proxy‑based 響應式系統 讓開發者能以最直觀的方式宣告資料,框架自動追蹤依賴並與 Virtual DOM 緊密結合,完成「資料變 → UI 更新」的全自動循環。
- 掌握
reactive、ref、computed、watch等 API,並遵守最佳實踐(如避免直接改變 Prop、適時使用nextTick),即可寫出 效能佳、維護性高 的 Vue 應用。
透過本文的概念與範例,你應該已經能夠在自己的專案中正確運用 Virtual DOM 與響應式系統,打造更快、更可靠的前端體驗。祝開發順利!