Vue 3 實戰:掌握 defineModel 輕鬆實現元件 v-model 雙向綁定
在以前的 Vue 版本中,要在元件上實現 v-model 需要手動處理 props (modelValue) 與 emits (update:modelValue),過程相當繁瑣。
自從 Vue 3.4+ 推出了 defineModel 之後,我們可以用更直覺、更簡潔的方式在子元件中建立 雙向綁定 的狀態,讓父子元件之間的數據同步像魔法一樣自然。
基礎語法:從單向變雙向
透過 defineModel(),子元件可以像使用一般的 ref() 一樣去修改資料,而這些修改會自動同步回父元件。
下(子)層
const counter = defineModel();<input type="button" value="-" @click="counter--">
<span v-text="counter"></span>
<input type="button" value="+" @click="counter++">上(父)層
<Child v-model="counter" />進階實戰:使用物件形式進行嚴謹宣告
就像 props 一樣,defineModel 也支援物件配置,讓您可以針對綁定的資料進行 型別檢查、預設值設定 或 必填限制。這在開發多人協作的元件庫時尤為重要。
const counter = defineModel({
required: true, // 規範必須傳入
type: Number, // 規範必須為數字
default: 10, // 若父層未傳值時的初始預設值
});為什麼要使用物件宣告?
- 安全性:確保傳入的資料型別正確(例如:計數器不應該接收到字串)。
- 可讀性:其他開發者看到代碼時,能立刻知道這個
v-model預期的資料格式。 - 防錯警告:當傳入資料不符合規範時,Vue 會在主控台發出警告。
總結:defineModel 的優勢
- 簡化代碼:不再需要同時撰寫
props與emits。 - 直覺操作:在子元件中修改
counter.value就會自動觸發事件通知父層。 - 完全相容:完美支援 Vue 的 響應式系統,讓元件封裝更加模組化。
實戰建議
如果您正在使用 Vue 3.4 以上的版本,請務必將現有的 props + emit() 模式遷移至 defineModel,這將顯著減少元件的模板程式碼。
多個 v-model:具名綁定
傳入名稱就能建立具名的 v-model,讓一個元件同時同步多份資料,常見於表單類元件:
// 子層
const title = defineModel('title');
const content = defineModel('content');<!-- 父層 -->
<PostEditor v-model:title="post.title" v-model:content="post.content" />對照:3.4 以前的寫法
理解底層有助於除錯,defineModel 其實就是下面這段的語法糖:
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。