Vue 核心觀念:MVVM、宣告式渲染與單向資料流
如果面試官問:「你怎麼理解 Vue?」很多人會回答:「Vue 是一個 MVVM 框架。」這句話不算錯,但如果只停在這裡,就很容易被追問打穿。
比較好的回答是:Vue 借用了 MVVM 的心智模型,讓開發者把重點放在狀態與畫面的關係上,而不是手動操作 DOM。當資料改變時,Vue 會透過響應式系統追蹤依賴,並自動安排畫面更新。
一、 必考觀念
1. MVVM 是什麼?
MVVM 是 Model、View、ViewModel 的縮寫:
| 角色 | 職責 | 在 Vue 中的大致對應 |
|---|---|---|
| Model | 應用資料與業務狀態 | ref、reactive、Pinia store、API 回傳資料 |
| View | 使用者看到的畫面 | Template、DOM、CSS |
| ViewModel | 連接資料與畫面的中介層 | Vue 元件實例、setup 回傳狀態、響應式系統 |
用一句話講:MVVM 的核心是讓資料狀態與畫面呈現建立自動同步關係。
在傳統 DOM 操作裡,我們常會這樣寫:
const count = 1
document.querySelector('#count').textContent = count當 count 改變時,我們還要記得再操作一次 DOM:
count = count + 1
document.querySelector('#count').textContent = count在 Vue 裡,我們描述的是資料和畫面的關係:
<template>
<button @click="count++">
{{ count }}
</button>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(1)
</script>開發者不需要直接告訴 DOM 怎麼改。只要 count 改變,Vue 就會負責把畫面更新到正確狀態。
2. Vue 是嚴格的 MVVM 嗎?
更精準地說,Vue 是 受到 MVVM 啟發的前端框架,不必把它硬套成教科書裡的 MVVM。
原因是 Vue 元件同時包含 Template、狀態、事件處理與部分邏輯,不是完全把 View、ViewModel、Model 分成三個獨立層。實務上,我們使用 MVVM 是為了建立心智模型:
- 資料是畫面的來源。
- 畫面由資料推導出來。
- 使用者操作會觸發事件,事件再修改資料。
- 資料變更後,框架負責更新畫面。
這比「Vue 就是 MVVM」更像一個能在面試中站得住的答案。
3. 宣告式渲染是什麼?
宣告式渲染的意思是:你描述「畫面應該長什麼樣」,而不是一步一步命令瀏覽器「現在要改哪個 DOM」。
例如:
<template>
<p v-if="isLogin">歡迎回來,{{ username }}</p>
<button v-else @click="login">登入</button>
</template>這段程式碼描述的是:
isLogin為true時,顯示歡迎訊息。isLogin為false時,顯示登入按鈕。
至於中間 DOM 怎麼建立、怎麼移除、怎麼更新文字節點,是 Vue 內部處理的事情。
4. 單向資料流是什麼?
Vue 的元件資料流通常是單向的:父元件透過 Props 把資料傳給子元件,子元件透過 Emit 通知父元件發生了什麼事。
<!-- Parent.vue -->
<template>
<UserCard
:user="user"
@rename="handleRename"
/>
</template><!-- UserCard.vue -->
<script setup lang="ts">
defineProps<{
user: {
name: string
}
}>()
const emit = defineEmits<{
rename: [name: string]
}>()
function submitName(name: string) {
emit('rename', name)
}
</script>這樣做的好處是資料來源清楚。當畫面出錯時,比較容易追蹤狀態是從哪裡來、又是在哪裡被改掉。
5. 雙向綁定和單向資料流衝突嗎?
不衝突。
v-model 看起來像雙向綁定,但在元件上本質是語法糖。它可以展開成:
<CustomInput
:model-value="title"
@update:model-value="title = $event"
/>也就是:
- 父元件把
title傳給子元件。 - 子元件發出
update:model-value事件。 - 父元件收到事件後更新自己的狀態。
所以 v-model 並不是讓子元件直接任意修改父元件狀態,而是把「傳值」與「更新事件」包成比較好寫的形式。
二、 追問題庫
Q1:Vue 為什麼不建議子元件直接修改 Props?
因為 Props 的來源在父元件。子元件如果直接修改 Props,會讓資料流變得混亂,也可能在父元件重新渲染時被覆蓋。
正確做法通常有三種:
- 子元件透過
emit通知父元件更新。 - 子元件建立本地狀態,將 Props 當作初始值。
- 多層共享狀態時,抽到 Pinia 或 Composable。
Q2:Vue 的資料改變後,畫面是同步更新嗎?
不是完全同步。
Vue 會把多次狀態變更放進更新佇列,等同一輪事件循環中的同步程式執行完,再批次更新 DOM。這能避免每次資料一改就立刻重繪,減少不必要的 DOM 操作。
如果你需要等 DOM 更新完成後再讀取畫面狀態,可以使用 nextTick:
import { nextTick, ref } from 'vue'
const visible = ref(false)
async function openPanel() {
visible.value = true
await nextTick()
// 這裡再讀取 DOM 尺寸會比較可靠
}Q3:MVVM 和 MVC 差在哪?
簡化理解:
| 模式 | 重點 |
|---|---|
| MVC | Controller 接收操作,更新 Model,再決定 View 如何變化 |
| MVVM | ViewModel 連接資料與畫面,資料變化會自動反映到 View |
在前端框架裡,不必過度糾結名詞邊界。面試時更重要的是說清楚:Vue 透過響應式系統與宣告式渲染,把「狀態變更」到「畫面更新」這段流程自動化。
Q4:Vue 和 jQuery 最大差異是什麼?
jQuery 的核心思維是「找到 DOM,然後修改它」。Vue 的核心思維是「描述狀態和畫面的關係,讓框架維護 DOM」。
例如列表更新:
// jQuery 思維:手動組 HTML、清空、插入
$('#list').html(items.map(item => `<li>${item.name}</li>`).join(''))<!-- Vue 思維:描述列表如何由資料產生 -->
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>當資料與互動變複雜時,Vue 的資料驅動畫面會比手動維護 DOM 更穩定。
Q5:Vue 的核心能力可以怎麼總結?
可以用四句話回答:
- Vue 用宣告式渲染描述資料與畫面的關係。
- Vue 用響應式系統追蹤狀態依賴。
- Vue 在資料變更後自動安排畫面更新。
- Vue 用元件化與單向資料流管理複雜 UI。
三、 實作題
題目:把命令式 DOM 操作改成 Vue 寫法
假設有一段傳統寫法:
<button id="toggle">切換</button>
<p id="message">目前關閉</p>let enabled = false
document.querySelector('#toggle').addEventListener('click', () => {
enabled = !enabled
document.querySelector('#message').textContent = enabled
? '目前開啟'
: '目前關閉'
})改成 Vue 時,可以這樣寫:
<template>
<button @click="enabled = !enabled">切換</button>
<p>{{ message }}</p>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
const enabled = ref(false)
const message = computed(() => {
return enabled.value ? '目前開啟' : '目前關閉'
})
</script>這裡的重點不是少寫幾行,而是責任改變了:
| 原本 DOM 寫法 | Vue 寫法 |
|---|---|
| 手動監聽 DOM 事件 | Template 綁定事件 |
| 手動保存狀態 | ref 保存狀態 |
| 手動更新文字節點 | computed 推導畫面文字 |
| 開發者維護 DOM | Vue 維護 DOM |
面試時可以補一句:當畫面越複雜,這種資料驅動的寫法越容易維護,因為 UI 是從狀態推導出來的,不是散落在各處的 DOM 操作結果。
四、 資深視角
1. 不要把 MVVM 當成萬用答案
資淺回答「Vue 是 MVVM 框架」通常可以過關,但資深一點的回答應該補上限制:
- Vue 不需要嚴格遵守傳統 MVVM 分層。
- Vue 元件內可能同時存在 View 與 ViewModel 的責任。
- 大型專案更重要的是元件邊界、狀態歸屬與資料流一致性。
所以 MVVM 是入門模型,不是架構銀彈。
2. 單向資料流是維護性的基礎
小型頁面裡,直接改資料通常感覺最快。但當元件層級變深、多人協作增加、狀態來源變多時,真正麻煩的是「不知道誰改了資料」。
資深工程師在設計 Vue 應用時,通常會先確認:
- 這個狀態應該屬於頁面、元件、Composable,還是全域 store?
- 子元件是純展示,還是可以管理自己的互動狀態?
v-model是讓 API 更好用,還是掩蓋了資料來源?- 狀態更新是否能被追蹤、測試與回放?
3. 宣告式不代表不用理解 DOM
Vue 幫我們處理大多數 DOM 更新,但不代表可以完全忽略瀏覽器行為。像是焦點控制、滾動位置、動畫、第三方套件整合、效能瓶頸,都還是需要理解真實 DOM。
好的 Vue 工程師不是只會寫 Template,而是知道什麼時候讓框架接手,什麼時候需要回到 DOM、事件循環與渲染成本來思考。
總結
這題的面試重點不是背出 MVVM 三個單字,而是說清楚 Vue 的核心心智模型:
| 核心觀念 | 面試回答重點 |
|---|---|
| MVVM | Vue 借用 MVVM 思想,讓資料與畫面建立自動同步關係 |
| 宣告式渲染 | 描述畫面應該長什麼樣,而不是手動操作 DOM |
| 響應式更新 | 狀態改變後,Vue 追蹤依賴並安排畫面更新 |
| 單向資料流 | Props 往下傳,事件往上通知,資料來源更清楚 |
v-model | 元件上的雙向綁定本質是 Props 加 Emit 的語法糖 |
一句比較完整的面試回答可以是:
Vue 是一個以資料驅動畫面的前端框架。它借用了 MVVM 的心智模型,讓開發者透過 Template 描述狀態與畫面的關係,再由響應式系統追蹤依賴並更新 DOM。實務上,我會把 Vue 理解成宣告式渲染、響應式狀態和元件化資料流的組合,而不是只把它簡化成 MVVM。