Skip to content

Vue 3 實戰:掌握 defineModel 輕鬆實現元件 v-model 雙向綁定

Vue 3 元件 v-model:defineModel()

在以前的 Vue 版本中,要在元件上實現 v-model 需要手動處理 props (modelValue) 與 emits (update:modelValue),過程相當繁瑣。

自從 Vue 3.4+ 推出了 defineModel 之後,我們可以用更直覺、更簡潔的方式在子元件中建立 雙向綁定 的狀態,讓父子元件之間的數據同步像魔法一樣自然。

基礎語法:從單向變雙向

透過 defineModel(),子元件可以像使用一般的 ref() 一樣去修改資料,而這些修改會自動同步回父元件。

下(子)層

js
const counter = defineModel();
html
<input type="button" value="-" @click="counter--">
<span v-text="counter"></span>
<input type="button" value="+" @click="counter++">

上(父)層

html
<Child v-model="counter" />

進階實戰:使用物件形式進行嚴謹宣告

就像 props 一樣,defineModel 也支援物件配置,讓您可以針對綁定的資料進行 型別檢查預設值設定必填限制。這在開發多人協作的元件庫時尤為重要。

js
const counter = defineModel({
  required: true, // 規範必須傳入
  type: Number, // 規範必須為數字
  default: 10, // 若父層未傳值時的初始預設值
});

為什麼要使用物件宣告?

  • 安全性:確保傳入的資料型別正確(例如:計數器不應該接收到字串)。
  • 可讀性:其他開發者看到代碼時,能立刻知道這個 v-model 預期的資料格式。
  • 防錯警告:當傳入資料不符合規範時,Vue 會在主控台發出警告。

總結:defineModel 的優勢

  • 簡化代碼:不再需要同時撰寫 propsemits
  • 直覺操作:在子元件中修改 counter.value 就會自動觸發事件通知父層。
  • 完全相容:完美支援 Vue 的 響應式系統,讓元件封裝更加模組化。

實戰建議

如果您正在使用 Vue 3.4 以上的版本,請務必將現有的 props + emit() 模式遷移至 defineModel,這將顯著減少元件的模板程式碼。

多個 v-model:具名綁定

傳入名稱就能建立具名的 v-model,讓一個元件同時同步多份資料,常見於表單類元件:

js
// 子層
const title = defineModel('title');
const content = defineModel('content');
html
<!-- 父層 -->
<PostEditor v-model:title="post.title" v-model:content="post.content" />

對照:3.4 以前的寫法

理解底層有助於除錯,defineModel 其實就是下面這段的語法糖:

js
const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);

// 修改時要自己發事件通知父層
function increment() {
  emit('update:modelValue', props.modelValue + 1);
}

常見問題

defineModel 需要哪個 Vue 版本?

Vue 3.4 起正式穩定。3.4 以前的版本必須自己宣告 modelValue prop 並發出 update:modelValue 事件。

defineModel 回傳的值要加 .value 嗎?

它回傳的是一個 ref,在 <script> 中讀寫要加 .value,在模板中則會自動解包,直接使用即可。

一個元件可以有多個 v-model 嗎?

可以。傳入名稱即可建立具名 v-model,例如:defineModel('title') 對應父層的 v-model:title

defineModel 與 props 有什麼不同?

props 是單向的,子元件不能修改;defineModel 在底層仍是 props 加 emit,但 Vue 幫您把修改自動轉成事件送回父層,因此可以直接賦值。

父層沒有用 v-model 綁定時會怎麼樣?

此時它會退化成元件的內部狀態,仍然可以正常讀寫,只是不會同步到父層。可以搭配 default 設定初始值。

什麼時候用 emit 就好,不必用 v-model?

只是要通知父層發生了某件事,例如:按下刪除、送出表單,用 emit 語意更清楚;需要持續同步同一份資料時才用 v-model

延伸閱讀