前言

你是否曾經在寫 Vue 組件時,為了實現一個簡單的雙向綁定(v-model),不得不反覆編寫 props 接收 modelValue,再在 emits 裏聲明 update:modelValue,最後還要寫一個計算屬性或者事件處理函數來觸發更新?

這種“樣板代碼”不僅由於繁瑣降低了開發效率,還打斷了代碼的邏輯流。

隨着 Vue 3.4+defineModel() 宏終於轉正!它就像一把瑞士軍刀,將過去散落在各處的雙向綁定邏輯收斂為一行優雅的代碼。今天,我們就來深度拆解這個能夠顯著提升代碼幸福感的特性。


什麼是 defineModel()

簡單來説,defineModel() 是 Vue 3.4+ script setup 語法糖中的一個宏。它的核心作用是簡化父子組件之間的雙向數據綁定。

在它出現之前,Vue 提倡“單向數據流”。雖然這很穩健,但在構建表單組件或通用 UI 庫時,我們極其需要一種簡便的方式來同步狀態。defineModel() 返回一個 ref當你修改這個 ref 的值時,Vue 會自動幫你觸發更新事件,通知父組件修改數據。


核心用法:從繁瑣到極簡

讓我們通過代碼直觀地感受一下它的魔力:假設我們有一個自定義的輸入框組件 MyInput.vue

傳統方式 (Vue 3.3 及以前):

<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

function handleInput(e) {
  emit('update:modelValue', e.target.value)
}
</script>

<template>
  <input :value="props.modelValue" @input="handleInput" />
</template>

使用 defineModel() (Vue 3.4+):

<script setup>
// 這一行代碼不僅定義了 prop,還處理了 emit
const model = defineModel()
</script>

<template>
  <input v-model="model" />
</template>

解析:const model = defineModel() 返回的 model 是一個 ref

  • 讀取時:它返回父組件傳遞進來的 modelValue prop。
  • 寫入時:它會自動觸發 emit('update:modelValue', newValue)

進階場景:它比你想象的更強大

defineModel 不僅僅是省代碼,它還支持多種高級配置。

1. 具名 v-model

如果你的組件需要多個雙向綁定(例如一個範圍選擇器,有 start 和 end),你可以傳遞一個名字字符串:

// 父組件: <RangePicker v-model:start="startTime" v-model:end="endTime" />

// 子組件
const start = defineModel('start')
const end = defineModel('end')

2. 定義 Prop 選項

既然本質是 Prop,你當然可以像 defineProps 一樣定義類型和默認值:

const count = defineModel({ type: Number, default: 0 })
const isVisible = defineModel('visible', { type: Boolean, required: true })

3. 處理修飾符

比如你使用了 <MyInput v-model.trim="msg" />,如何在子組件裏獲知 .trim 修飾符?

// 解構返回值:第一個是值,第二個是修飾符對象
const [modelValue, modelModifiers] = defineModel()

// 如果使用了 v-model.trim,這裏 log 輸出 { trim: true }
console.log(modelModifiers)

與傳統方式對比

為了讓你更直觀地理解它的優勢,我們來看一下對比表:

維度

props + emit

defineModel

代碼量

多(需分別定義 Prop, Emit, Handler)

極少(通常只需一行)

心智負擔

高(需時刻謹記單向數據流的手動處理)

低(像操作本地 ref 一樣自然)

可讀性

邏輯分散在 <script> 不同部分

邏輯內聚

類型推斷

需手動標註 Prop 和 Emit 類型

自動推斷,TS 支持極佳

底層原理

顯式的數據傳遞與事件觸發

編譯器宏,編譯時展開為 Prop/Emit

最佳適用場景

並不是所有 Props 都需要換成 defineModel。以下場景是它的“殺手級”應用領域:

  1. 表單類組件封裝:所有涉及到 Input, Select, Checkbox, Radio
  2. UI 狀態控制:例如 Modal(彈窗)、Drawer(抽屜)、Switch(開關)。通常父組件控制 v-model:visible,子組件內部關閉時直接 visible.value = false 即可,非常直觀。
  3. 複雜的交互組件:如日期選擇器、富文本編輯器等,需要頻繁與父組件同步狀態,且可能存在多個同步維度(如內容、格式、選區等)。

注意事項

儘管 defineModel 很香,但在使用時仍需注意以下幾點:

  1. 版本要求:你需要 Vue 3.4+ 版本。如果你還在用 3.3,這是一個處於實驗性階段的功能(需配置開啓)。
  2. 單向流原則:雖然寫法上像雙向綁定,但底層依然遵循 Vue 的單向數據流原則(通過事件通知父組件更新)。不要產生“子組件直接修改了父組件數據”的錯覺。
  3. 避免濫用:對於僅僅用來展示的數據(只讀),依然應該使用 defineProps,以保持語義的明確性。


Vue 3.4+defineModel() 不僅僅是一個語法糖,它是 Vue 團隊在提升開發者體驗道路上的重要一步。它消除了樣板代碼的噪音,讓我們可以專注於組件的業務邏輯,而不是數據傳遞的管道搭建。