本文 AI 產出,尚未審核

Vue3 元件通信:v-model 雙向綁定(父子元件)


簡介

在單頁應用程式中,父子元件之間的資料同步是最常見的需求之一。Vue 3 透過 v-model 為我們提供了「雙向綁定」的直覺寫法,讓父層可以直接控制子層的狀態,同時子層的變更也會自動回傳給父層。

除了最基礎的 v-model(綁定 modelValue + update:modelValue 事件),Vue 3 仍支援 多個 v-model修飾符、以及 .sync 語法,讓開發者能以最少的樣板程式碼,建立可重用、易維護的 UI 元件。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你一步步掌握 父子元件雙向綁定 的核心技巧,並提供實務應用情境,幫助你在專案中快速上手。


核心概念

1. v-model 的基本原理

在 Vue 3 中,v-model 其實是 語法糖,最終會被編譯成以下兩個操作:

<!-- 父層 -->
<my-input v-model="msg" />

會被轉譯為:

<my-input :modelValue="msg" @update:modelValue="val => msg = val" />
  • modelValue 為子元件接受的 prop
  • update:modelValue 為子元件發出的 事件,父層收到後更新本地資料

重點:子元件 不可直接修改 modelValue,必須透過 $emit('update:modelValue', newValue) 來回傳新值。


2. 建立最簡單的雙向綁定元件

範例 1:自訂文字輸入元件

<!-- MyInput.vue -->
<template>
  <input
    :value="modelValue"
    @input="onInput"
    placeholder="請輸入文字"
  />
</template>

<script setup>
import { defineProps, defineEmits } from 'vue'

const props = defineProps({
  modelValue: String   // 接收父層傳入的值
})

const emit = defineEmits(['update:modelValue'])

function onInput(event) {
  // 透過 $emit 回傳新值給父層
  emit('update:modelValue', event.target.value)
}
</script>
<!-- Parent.vue -->
<template>
  <MyInput v-model="text" />
  <p>你輸入的是:{{ text }}</p>
</template>

<script setup>
import { ref } from 'vue'
import MyInput from './MyInput.vue'

const text = ref('')
</script>

此範例展示 最小化 的雙向綁定流程,父層的 text 會隨著子元件的輸入即時更新。


3. 自訂 v-model 的 Prop 與 Event 名稱

Vue 3 允許在 <child v-model:foo="bar" /> 的形式,自訂 綁定的 prop 與事件名稱,讓同一個元件可以同時支援多個雙向綁定。

範例 2:支援 v-model:checkedv-model:label

<!-- MySwitch.vue -->
<template>
  <label>
    <input type="checkbox"
           :checked="checked"
           @change="onCheckedChange" />
    <span>{{ label }}</span>
  </label>
</template>

<script setup>
import { defineProps, defineEmits } from 'vue'

const props = defineProps({
  checked: Boolean,   // 由 v-model:checked 綁定
  label: String       // 由 v-model:label 綁定
})

const emit = defineEmits(['update:checked', 'update:label'])

function onCheckedChange(e) {
  emit('update:checked', e.target.checked)
}
</script>
<!-- Parent.vue -->
<template>
  <MySwitch
    v-model:checked="isOn"
    v-model:label="title"
  />
  <p>開關狀態:{{ isOn }},標題:{{ title }}</p>
</template>

<script setup>
import { ref } from 'vue'
import MySwitch from './MySwitch.vue'

const isOn = ref(false)
const title = ref('關閉')
</script>

技巧:在子元件內部,checkedlabel 仍然是 只讀 的 prop,若需要在子元件內部改變,仍須使用 $emit


4. 多個 v-model(同時綁定多個狀態)

從 Vue 3.2 起,v-model 支援 多個同時綁定,非常適合像 表單元件日期範圍選擇器 等需要一次傳遞多個值的情境。

範例 3:日期範圍選擇器

<!-- DateRangePicker.vue -->
<template>
  <div class="range-picker">
    <input type="date"
           :value="start"
           @input="val => emit('update:start', val.target.value)" />
    <span>~</span>
    <input type="date"
           :value="end"
           @input="val => emit('update:end', val.target.value)" />
  </div>
</template>

<script setup>
import { defineProps, defineEmits } from 'vue'

const props = defineProps({
  start: String,
  end: String
})

const emit = defineEmits(['update:start', 'update:end'])
</script>
<!-- Parent.vue -->
<template>
  <DateRangePicker
    v-model:start="rangeStart"
    v-model:end="rangeEnd"
  />
  <p>選取區間:{{ rangeStart }} ~ {{ rangeEnd }}</p>
</template>

<script setup>
import { ref } from 'vue'
import DateRangePicker from './DateRangePicker.vue'

const rangeStart = ref('')
const rangeEnd = ref('')
</script>

說明:父層只需要在 v-model:startv-model:end 上分別指定對應的變數,子元件內部則使用 update:startupdate:end 事件回傳。


5. 使用 .sync 修飾符(舊版寫法的兼容)

在 Vue 2 時,.sync 是常見的雙向綁定方式。Vue 3 仍保留此語法,以方便 從舊專案遷移

<!-- Parent.vue -->
<template>
  <MyInput :value.sync="msg" />
</template>

等同於:

<my-input :value="msg" @update:value="val => msg = val" />

提醒.sync 只能在 單一 prop 上使用,若要同時綁定多個值,仍建議使用 v-model:xxx


6. v-model 修飾符:numbertrimlazy

Vue 內建三個常用的修飾符,用以在 資料傳遞前 自動處理類型或空白:

修飾符 功能 範例
.number 將字串轉為數字 <my-input v-model.number="age" />
.trim 去除前後空白 <my-input v-model.trim="name" />
.lazy 改為 change 事件觸發 <my-input v-model.lazy="msg" />

在自訂元件中,修飾符會自動套用在 update:modelValue 事件的 payload 上,開發者無需額外處理。


常見陷阱與最佳實踐

陷阱 說明 解決方式
直接變更 Prop 子元件內部直接 props.modelValue = newVal 會觸發 Vue 警告,且不會同步回父層。 使用 $emit('update:modelValue', newVal),或在 <script setup> 中使用 defineEmits
命名不一致 v-model 預設對應 modelValue,若子元件改用 value,父層仍寫 v-model 會失效。 明確在子元件中宣告 modelValue,或在父層使用 v-model:customProp
多個 v-model 事件衝突 同一個 prop 同時使用 v-model.sync 會產生重複更新。 統一使用 v-model 系統,避免混用。
事件名稱拼寫錯誤 update:modelValue 必須全小寫,若寫成 update:ModelValue 會無法觸發。 使用 IDE 自動補全或常量 UPDATE_MODEL_VALUE = 'update:modelValue'
父層資料不是響應式 父層傳入的值若是普通變量(未使用 ref/reactive),子元件變更不會回傳。 確保父層變數為 響應式refreactivecomputed)。

最佳實踐

  1. 保持單向資料流:子元件只 發射 事件,絕不直接改變 props
  2. 使用 defineProps & defineEmits:在 <script setup> 中明確宣告,讓 TypeScript 能正確推斷。
  3. 命名慣例:自訂 v-model 時,建議使用 名詞(如 checkedvalue)作為 prop,update: 前綴作為事件。
  4. 避免過度綁定:若只需要單向傳遞,改用 props + $emit('change'),減少不必要的雙向綁定。
  5. 測試雙向綁定:使用 @vue/test-utilssetValuetrigger 方法驗證父子同步行為。

實際應用場景

場景 為什麼適合使用 v-model
表單元件庫(如自訂 <Select>、<DatePicker> 父層只需要寫 <MySelect v-model="selected">,即可自動同步選取結果。
彈窗/對話框(控制開關) 父層透過 v-model:visible 控制顯示,子元件內部點擊「關閉」即 $emit('update:visible', false)
可編輯表格(每列的編輯狀態) 每列子元件 v-model:isEditing,父層維護編輯陣列,無需額外 watch
多語系切換(切換語言後即時更新 UI) 子元件使用 v-model:locale,父層改變時自動傳遞,子元件內部只負責渲染。
父子協同的圖表設定(如調整比例、顏色) 父層保存設定物件,子元件 v-model:config,即時回傳變更,圖表即時重新渲染。

總結

  • v-model 在 Vue 3 中是 雙向綁定的核心語法糖,背後等同於 :modelValue + @update:modelValue
  • 透過 自訂 prop/event 名稱v-model:foo)以及 多個 v-model,我們可以在同一個子元件裡同時管理多筆狀態。
  • 使用 .number.trim.lazy 等修飾符,能在資料傳遞前自動完成常見的類型或字串處理。
  • 避免直接改變 Prop保持命名一致確保父層變數是響應式,是避免常見錯誤的關鍵。
  • 在實務開發中,v-model 是構建 可重用表單元件、彈窗控制、即時同步 UI 的最佳工具,只要遵守單向資料流的原則,就能寫出既簡潔又易維護的程式碼。

掌握了上述概念與技巧後,你就能在 Vue 3 專案中,輕鬆實作父子元件的雙向綁定,提升開發效率與程式品質。祝你寫程式快樂,元件溝通無礙!