人人都会AI编程

v-model 双向绑定:语法糖本质与自定义 v-model

更新时间:2026-07-11

v-model 是 Vue 中最常用的指令之一,它让表单输入和数据状态之间的同步变得极其简单。但它的本质只是一个语法糖,理解了它背后的机制,你就能在组件封装中灵活运用,甚至实现多个双向绑定。

语法糖本质:一个属性绑定加一个事件监听

在原生表单元素上使用 v-model="message",Vue 会根据元素类型自动展开为对应的属性绑定和事件处理:

<!-- 在文本输入框上 -->
<input v-model="message" />

<!-- 等价于 -->
<input
  :value="message"
  @input="message = $event.target.value"
/>

如果是复选框,它会处理 checked 属性和 change 事件;如果是单选按钮或选择框,也有相应的展开规则。对于开发者来说,v-model 只是一个简洁的写法,省去了手动绑定属性和事件的麻烦。

在组件上使用 v-model

v-model 还可以用在自定义组件上,实现父子组件之间的双向数据流。这是封装表单控件、对话框、开关等组件的核心手段。

默认情况:Vue 3 中,v-model 在组件上的默认展开是:

  • propmodelValue
  • 事件update:modelValue

也就是说:

<MyComponent v-model="someValue" />

会被展开为:

<MyComponent
  :modelValue="someValue"
  @update:modelValue="newValue => someValue = newValue"
/>

这就要求子组件内接收名为 modelValue 的 prop,并在需要更新父组件数据时,触发 update:modelValue 事件。

子组件示例:封装一个自定义输入框

<!-- MyInput.vue -->
<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

父组件使用起来和原生表单元素一样自然:

<MyInput v-model="form.name" />

自定义多个 v-model 绑定

Vue 3 支持为 v-model 指定参数,让你在一个组件上实现多个双向绑定。语法是 v-model:参数名="数据",它会展开为 :参数名="数据"@update:参数名="..."

典型场景:封装一个带标题和内容的模态框,父组件需要同时控制其显示状态和显示文本。

<!-- 父组件 -->
<MyModal
  v-model:visible="isShow"
  v-model:title="modalTitle"
/>

子组件需要定义对应的 props 和 emits:

<!-- MyModal.vue -->
<template>
  <div v-if="visible" class="modal">
    <h2>{{ title }}</h2>
    <button @click="$emit('update:visible', false)">关闭</button>
  </div>
</template>

<script setup>
defineProps({
  visible: Boolean,
  title: String
})
defineEmits(['update:visible', 'update:title'])
</script>

注意:不带参数的 v-model="data" 依然是 modelValue,与 v-model:modelValue 完全等价。

自定义修饰符

v-model 还能配合修饰符使用,比如 .trim.number.lazy 是内置的。在组件上,你还可以自定义修饰符,让组件感知到父组件添加的修饰符并做出相应处理。

修饰符会通过 prop 传递:对于默认的 v-model,修饰符所在 prop 名为 modelModifiers;对于带参数的 v-model:title,修饰符 prop 名为 titleModifiers

示例:自定义输入框,支持一个 capitalize 修饰符,自动将输入内容首字母大写。

<!-- MyInput.vue -->
<template>
  <input :value="modelValue" @input="handleInput" />
</template>

<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { type: Object, default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])

function handleInput(e) {
  let value = e.target.value
  if (props.modelModifiers.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }
  emit('update:modelValue', value)
}
</script>

父组件使用:

<MyInput v-model.capitalize="text" />

capitalize 修饰符存在时,子组件就会在触发更新前自动转换数据。

v-model 与表单校验结合

在实际项目中,v-model 经常与表单校验库(如 VeeValidate 或组件库内置校验)结合使用。这时双向绑定仍然是最基础的数据通道,校验逻辑通过规则对象或函数来附加,并不会破坏 v-model 的简洁性。

总结v-model 只是一层语法糖,但理解了它的本质(prop + event)之后,你可以在组件封装中创造出极为顺滑的双向绑定体验。多个 v-model 和自定义修饰符让组件 API 更加语义化,减少了冗余的 prop 和事件声明,这正是 Vue 3 在组件通信上的一大改进。