本文 AI 產出,尚未審核

Vue3 – 指令(Directives)

主題:v-model 雙向綁定


簡介

在 Vue3 中,v-model 是最常用的指令之一,它負責在 表單元素資料狀態 之間建立雙向綁定(Two‑Way Binding)。透過 v-model,開發者只需要在模板上寫一行指令,就能自動同步使用者的輸入與組件的 datarefreactive 物件,省去繁瑣的事件監聽與手動賦值程式碼。

雙向綁定不只是簡化表單處理,更是 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-modelpropevent

Vue3 允許在自訂元件上使用 v-model,但預設的 propmodelValueeventupdate: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-modelv-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-modelreactive 結合

當使用 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-modelcheckedlabel

<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-modelvalue 同時使用 同時寫 v-model="foo":value="bar",會產生衝突,v-model 會覆寫 value 只保留 v-model,若需要預設值請在資料初始化時設定。
修飾符不適用於自訂元件 .trim.number.lazy 只會在原生元素上自動處理,對自訂元件不生效。 在自訂元件內自行實作相同的行為(如在 @input 前做 Number(value)),或在父層使用計算屬性包裝。
v-modelwatch 重複 同時使用 v-modelwatch 監聽同一屬性,可能導致不必要的重複執行。 若只需要「即時」回應,直接在模板或 computed 中處理;若需要副作用,保留 watch,但避免在 watch 中再手動更新同一屬性。
表單大型化時效能問題 大量 <input v-model> 會產生大量的事件監聽,影響效能。 使用 v-model.lazy 減少更新頻率,或將表單拆分為多個子元件,利用 v-on:input 手動控制更新時機。

最佳實踐

  1. 資料初始化:在 setup 中使用 refreactive 明確給定初始值,避免 undefined 帶來的 UI 抖動。
  2. 單向資料流:即使 v-model 看起來是雙向的,仍建議在子元件內只 發射事件,讓父層負責資料更新,保持 Vue 的單向資料流原則。
  3. 型別安全:配合 TypeScript 時,為 v-model 指定明確的類型(如 ref<number>(0)),並在自訂元件的 defineProps 中使用 PropType
  4. 表單驗證:將驗證邏輯抽離成 composable(如 useFormValidate),讓 v-model 只負責同步,驗證則在 watchsubmit 時觸發。

實際應用場景

場景 為何使用 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-modelv-model:xxx
  • 修飾符.lazy.number.trim
  • reactiveref 的配合

以及避免常見的陷阱、遵循最佳實踐,你就能在各種實務情境中自如運用 v-model,寫出 清晰、可維護且具備良好使用者體驗 的 Vue 應用程式。祝你在 Vue3 的旅程中玩得開心、寫得順手!