Vue3 – 指令(Directives)
主題:v-model 雙向綁定
簡介
在 Vue3 中,v-model 是最常用的指令之一,它負責在 表單元素 與 資料狀態 之間建立雙向綁定(Two‑Way Binding)。透過 v-model,開發者只需要在模板上寫一行指令,就能自動同步使用者的輸入與組件的 data、ref 或 reactive 物件,省去繁瑣的事件監聽與手動賦值程式碼。
雙向綁定不只是簡化表單處理,更是 Vue 反應式系統 的核心體驗。掌握 v-model 的運作原理與使用方式,能讓你在開發中更快速地構建互動式 UI,並在日後維護時保持程式碼的可讀性與一致性。
核心概念
1. v-model 的基本語法
在 Vue3 中,v-model 預設綁定 value 屬性與 input 事件(對於 <input>、<textarea>、<select> 等表單元素)。簡單範例如下:
<template>
<input v-model="message" placeholder="請輸入文字" />
<p>你輸入的是:{{ message }}</p>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('')
</script>
v-model="message"→message會自動接收使用者在 input 中的文字,並在message改變時即時更新 input 的值。
2. 自訂 v-model 的 prop 與 event
Vue3 允許在自訂元件上使用 v-model,但預設的 prop 為 modelValue,event 為 update:modelValue。以下示範一個簡易的自訂輸入元件:
<!-- MyInput.vue -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps({
modelValue: String
})
</script>
使用方式:
<template>
<MyInput v-model="username" />
<p>使用者名稱:{{ username }}</p>
</template>
<script setup>
import { ref } from 'vue'
import MyInput from './MyInput.vue'
const username = ref('')
</script>
重點:在自訂元件裡,
v-model其實是 語法糖,等同於:modelValue="username"+@update:modelValue="username = $event"。
3. 多個 v-model(v-model:xxx)
Vue3 支援在同一個元件上使用多個 v-model,只要為每個綁定指定不同的 prop 名稱即可。例如:
<!-- ToggleSwitch.vue -->
<template>
<label>
<input
type="checkbox"
:checked="checked"
@change="$emit('update:checked', $event.target.checked)"
/>
<span>{{ label }}</span>
</label>
</template>
<script setup>
defineProps({
checked: Boolean,
label: String
})
</script>
在父層使用:
<template>
<ToggleSwitch v-model:checked="isOn" v-model:label="title" />
<p>開關狀態:{{ isOn ? 'ON' : 'OFF' }}, 標題:{{ title }}</p>
</template>
<script setup>
import { ref } from 'vue'
import ToggleSwitch from './ToggleSwitch.vue'
const isOn = ref(false)
const title = ref('啟用功能')
</script>
4. 修飾符(.lazy、.number、.trim)
v-model 內建三個常用的 修飾符,可以直接在指令後加上:
| 修飾符 | 功能說明 |
|---|---|
.lazy |
改為在 change 事件(失去焦點或選擇)時才更新資料,而非每次 input。 |
.number |
自動將字串轉成數字(parseFloat),適用於 <input type="number">。 |
.trim |
自動去除前後空白,適合文字輸入。 |
範例:
<input v-model.lazy="age" type="number" />
<input v-model.number="price" />
<input v-model.trim="nickname" />
5. v-model 與 reactive 結合
當使用 reactive 物件時,可以直接對其屬性使用 v-model,Vue 會自動追蹤:
<template>
<input v-model="form.email" placeholder="Email" />
<input v-model="form.password" type="password" placeholder="Password" />
<pre>{{ form }}</pre>
</template>
<script setup>
import { reactive } from 'vue'
const form = reactive({
email: '',
password: ''
})
</script>
小技巧:若要在
reactive內部使用v-model的 修飾符,仍以同樣方式寫即可,修飾符會在賦值前先處理。
程式碼範例(實用示例)
範例 1:基本文字輸入 + .trim 修飾符
<template>
<input v-model.trim="title" placeholder="請輸入標題" />
<p>目前標題:{{ title }}</p>
</template>
<script setup>
import { ref } from 'vue'
const title = ref(' 初始值 ') // 會自動去除前後空白
</script>
範例 2:數字輸入 + .number + .lazy
<template>
<input v-model.number.lazy="quantity" type="number" placeholder="數量" />
<p>購買數量:{{ quantity }}</p>
</template>
<script setup>
import { ref } from 'vue'
const quantity = ref(1) // 使用 .lazy,只有離開欄位才更新
</script>
範例 3:自訂元件的雙向綁定(單一 v-model)
<!-- RatingStar.vue -->
<template>
<div class="stars">
<span
v-for="n in 5"
:key="n"
:class="{ active: n <= modelValue }"
@click="$emit('update:modelValue', n)"
>★</span>
</div>
</template>
<script setup>
defineProps({
modelValue: {
type: Number,
default: 0
}
})
</script>
<style scoped>
.stars span { cursor: pointer; color: #ccc; }
.stars span.active { color: #f39c12; }
</style>
父層使用:
<template>
<RatingStar v-model="rating" />
<p>目前評分:{{ rating }} 顆星</p>
</template>
<script setup>
import { ref } from 'vue'
import RatingStar from './RatingStar.vue'
const rating = ref(3)
</script>
範例 4:多個 v-model(checked 與 label)
<template>
<ToggleSwitch v-model:checked="enabled" v-model:label="switchLabel" />
<p>開關:{{ enabled ? '開' : '關' }}, 標籤:{{ switchLabel }}</p>
</template>
<script setup>
import { ref } from 'vue'
import ToggleSwitch from './ToggleSwitch.vue'
const enabled = ref(false)
const switchLabel = ref('自動更新')
</script>
範例 5:reactive 表單 + 動態驗證
<template>
<form @submit.prevent="handleSubmit">
<input v-model="form.name" placeholder="姓名" />
<input v-model.number="form.age" type="number" placeholder="年齡" />
<p v-if="error" class="error">{{ error }}</p>
<button type="submit">送出</button>
</form>
</template>
<script setup>
import { reactive, ref } from 'vue'
const form = reactive({ name: '', age: null })
const error = ref('')
function handleSubmit() {
if (!form.name) {
error.value = '請輸入姓名'
return
}
if (form.age <= 0) {
error.value = '年齡必須大於 0'
return
}
error.value = ''
console.log('表單資料:', { ...form })
}
</script>
<style scoped>
.error { color: red; margin-top: 0.5rem; }
</style>
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
直接修改 props |
在子元件內使用 v-model 時,若直接改寫 props(如 props.modelValue = ...),Vue 會在開發模式拋出警告。 |
使用 $emit('update:modelValue', newValue),或在父層使用 ref/reactive 讓資料流向單向。 |
v-model 與 value 同時使用 |
同時寫 v-model="foo" 與 :value="bar",會產生衝突,v-model 會覆寫 value。 |
只保留 v-model,若需要預設值請在資料初始化時設定。 |
| 修飾符不適用於自訂元件 | .trim、.number、.lazy 只會在原生元素上自動處理,對自訂元件不生效。 |
在自訂元件內自行實作相同的行為(如在 @input 前做 Number(value)),或在父層使用計算屬性包裝。 |
v-model 與 watch 重複 |
同時使用 v-model 與 watch 監聽同一屬性,可能導致不必要的重複執行。 |
若只需要「即時」回應,直接在模板或 computed 中處理;若需要副作用,保留 watch,但避免在 watch 中再手動更新同一屬性。 |
| 表單大型化時效能問題 | 大量 <input v-model> 會產生大量的事件監聽,影響效能。 |
使用 v-model.lazy 減少更新頻率,或將表單拆分為多個子元件,利用 v-on:input 手動控制更新時機。 |
最佳實踐:
- 資料初始化:在
setup中使用ref或reactive明確給定初始值,避免undefined帶來的 UI 抖動。 - 單向資料流:即使
v-model看起來是雙向的,仍建議在子元件內只 發射事件,讓父層負責資料更新,保持 Vue 的單向資料流原則。 - 型別安全:配合 TypeScript 時,為
v-model指定明確的類型(如ref<number>(0)),並在自訂元件的defineProps中使用PropType。 - 表單驗證:將驗證邏輯抽離成 composable(如
useFormValidate),讓v-model只負責同步,驗證則在watch或submit時觸發。
實際應用場景
| 場景 | 為何使用 v-model |
|---|---|
| 即時搜尋欄 | 輸入文字即時更新 searchQuery,配合 watch 觸發 API 請求,實現「打字即搜尋」體驗。 |
| 表單編輯 | 編輯資料時,使用 v-model 直接綁定到 form 物件,提交時只需取出整個物件即可,降低欄位逐一取值的繁瑣。 |
| 自訂 UI 元件(如日期選擇器、分頁器) | 透過 v-model 讓外部使用者只需寫 <DatePicker v-model="date" />,內部自行處理 UI 與事件,保持 API 一致性。 |
| 多選清單 | 結合 v-model 與 <select multiple>,直接取得陣列型別的選取結果,簡化資料處理。 |
| 即時驗證 | v-model 搭配 .lazy 可在使用者離開欄位時才驗證,減少不必要的驗證次數,提升使用者體驗。 |
總結
v-model 是 Vue3 中最具代表性的 雙向綁定指令,它不僅讓表單操作變得簡潔,也為自訂元件提供了統一的 API。透過了解:
- 基本語法與 自訂
modelValue - 多個
v-model(v-model:xxx) - 修飾符(
.lazy、.number、.trim) - 與
reactive、ref的配合
以及避免常見的陷阱、遵循最佳實踐,你就能在各種實務情境中自如運用 v-model,寫出 清晰、可維護且具備良好使用者體驗 的 Vue 應用程式。祝你在 Vue3 的旅程中玩得開心、寫得順手!