Appearance
宣告式渲染與響應式心智
上一章我們已經用 Vite 建立好第一個 Vue 專案。接下來,先把畫面與資料之間的關係看清楚。先別急著找 DOM:用 Vue 時,我們更常做的是修改資料,剩下的畫面更新交給 Vue。
從資料驅動畫面開始
在傳統 DOM 操作中,我們常會先找到某個元素,再把文字塞進去。Vue 的寫法反過來:我們在 <script setup> 準備資料,在 <template> 描述畫面要如何呈現資料。
請打開第一章建好的 create-vue 專案,先把 src/App.vue 改成下面這個最小範例:
vue
<script setup>
const message = 'Hello Vue!'
</script>
<template>
<main class="app">
<h1>{{ message }}</h1>
</main>
</template>
<style scoped>
.app {
padding: 2rem;
}
</style>{{ message }} 稱為文字插值。Vue 會讀取 message 的值,並把它顯示在畫面上。這裡還沒有任何互動,不過已經是接下來會反覆看到的分工:資料準備在 <script setup>,畫面則寫在 <template>。
讓資料變成響應式
如果資料會改變,就要讓 Vue 追蹤它。Composition API 裡最常用的起點是 ref():
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<main class="app">
<p>目前數字:{{ count }}</p>
<button type="button" @click="increment">
加一
</button>
</main>
</template>
<style scoped>
.app {
padding: 2rem;
}
</style>目前數字:0
ref(0) 會建立一個可被 Vue 追蹤的響應式資料。按下按鈕時,increment() 修改 count.value,Vue 會自動把畫面上的 {{ count }} 更新成新的值。
常見錯誤:忘記 .value
在 <script setup> 裡,ref 的值要透過 .value 存取:
js
const count = ref(0)
count++ // 錯誤:count 是 const,重新指派會拋出 TypeError
count.value++ // 正確:這是在改 ref 裡面的值要注意的是,count++ 並不是「改到 ref 裡面的值」,而是試圖把 count 這個變數重新指派成別的東西。因為我們用 const 宣告,JavaScript 會直接拋出錯誤,這反而是好事:錯誤訊息會提醒我們忘了 .value。
這也回答了為什麼 ref() 通常搭配 const,而不是 let。const 限制的是 count 這個變數不能改指向別的東西,並不會讓 ref 裡的值變成唯讀,因此 count.value++ 完全合法。改用 let 在語法上也能運作,卻會放行下面這類重新指派:
js
let count = ref(0)
count = ref(10) // 合法,但已經換掉原本的 ref
count = 10 // 也合法,count 從此不再是 ref響應式狀態通常只需要修改 .value,不需要換掉 ref 本身,所以用 const 能把意外提早擋下來。除非真的需要重新指派變數,否則預設使用 const 會更安全,也更容易看出資料的身分沒有改變。
但在 <template> 裡,Vue 會自動解包 ref,所以畫面上寫 {{ count }} 即可,不需要寫成 {{ count.value }}。
小提醒:還有一個 reactive()
Vue 也提供 reactive() 建立響應式物件。本章先統一使用較容易追蹤資料來源的 ref();兩者的差異與選擇方式,第三章說明響應式系統時會完整比較。
模板自動解包
模板自動解包是初學者最容易混淆的地方。規則只有兩條:
- 在
<script setup>裡操作ref:要寫.value。 - 在
<template>裡顯示或綁定ref:直接寫變數名稱。
不過自動解包有個前提:它只適用於模板可以直接看到的頂層變數,也就是像本章這樣直接在 <script setup> 頂層宣告的 ref。如果 ref 被包在一般物件的屬性裡,模板運算式就不會自動解包它。本章的範例都是頂層宣告,可以放心套用這個規則;巢狀結構的細節,我們留到第三章討論響應式系統時再展開。
下面這個範例同時使用 ref 與模板插值:
vue
<script setup>
import { ref } from 'vue'
const name = ref('Kuro')
function rename() {
name.value = 'Vue Reader'
}
</script>
<template>
<main class="app">
<p>Hi, {{ name }}!</p>
<button type="button" @click="rename">
換名字
</button>
</main>
</template>
<style scoped>
.app {
padding: 2rem;
}
</style>按下按鈕後,我們沒有直接操作 DOM,也沒有自己去找 <p> 標籤。只要資料改變,畫面就會跟著資料重新呈現。
Vue 的更新流程
可以先用這張圖建立直覺:

這張圖先記住一件事:我們修改的是狀態,Vue 負責安排畫面更新。資料可能來自按鈕,也可能來自 API 或計時器;Vue 會批次處理這些變化,最後只更新必要的 DOM。
用 MVVM 理解這段流程
如果你接觸過 MVVM (Model-View-ViewModel),也可以借用它來理解前面的流程。三個角色大致如下:
| 角色 | 負責什麼 | 在 Vue 應用裡常見的對應 |
|---|---|---|
| Model | 應用程式的資料與領域規則 | API 回傳資料、一般 JavaScript 物件、Store |
| View | 使用者看到與操作的畫面 | <template> 描述的介面,以及最後產生的 DOM |
| ViewModel | 準備畫面需要的狀態、接收操作,並銜接 Model 與 View | 元件中的響應式狀態與操作函式,加上 Vue 的響應式與渲染機制 |
以前面的計數器為例,使用者在 View 按下按鈕後,元件的 increment() 會修改 count.value;Vue 追蹤到這個變化,再把新的數字更新到 View。這段元件邏輯就扮演了類似 ViewModel 的橋接角色。計數器很小,沒有另外抽出領域資料層;在真正的應用裡,Model 可能來自 API 或 Store,再由元件整理成畫面需要的狀態。
那麼,Vue.js 現在還算不算 MVVM 框架?
比較精確的說法是:Vue 的設計可以用 MVVM 幫助理解,但 Vue 並不要求應用程式嚴格遵循 MVVM 架構。
早期 Vue 文件常用 ViewModel 解釋 Vue instance;現代 Vue 則把自己定位為漸進式 JavaScript 框架,強調的是宣告式渲染、響應式系統與元件化。使用 Composition API 時,我們也不需要建立一個名為 ViewModel 的類別,ref、computed 與函式可以依功能自由組合。
所以,MVVM 在這裡是一張理解資料與畫面如何連動的地圖,不是每個 Vue 專案都必須照著套用的目錄規格。真正需要記住的仍是:畫面負責描述結果,程式修改狀態,Vue 負責把兩者同步起來。
接下來要介紹的模板、事件與 computed,都是建立在這個資料驅動畫面的觀念上。
