Vue的v-model本质上是:modelValue + @update:modelValue的语法糖。


显式写法适用于教学演示、需要透传多个v-model或添加额外逻辑的场景。


Vue3.4+引入的defineModel()新特性简化了双向绑定,自动处理props/emits,支持类型推导和修饰符,比传统写法更简洁高效。


推荐在Vue3.4+项目中使用defineModel(),它减少了代码量并提升可读性,是v-model的最佳实践方案。


v-model 的本质

v-model 其实是语法糖,它会自动展开为 :modelValue + @update:modelValue:

<!-- 这两种写法完全等价 -->
<BaseInput v-model="username" />
<BaseInput :modelValue="username" @update:modelValue="val => username = val" />

为什么示例中显式写出展开形式?

1. 展示透传机制

<template>
  <div class="custom-input">
    <label>{{ label }}</label>
    <!-- 显式写出可以清楚看到属性透传和事件透传 -->
    <BaseInput 
      v-bind="$attrs"
      :modelValue="modelValue" 
      @update:modelValue="$emit('update:modelValue', $event)"
    />
  </div>
</template>

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

如果用 v-model 简写:

<!-- 看起来简洁,但不够直观 -->
<BaseInput v-bind="$attrs" v-model="modelValue" />

2. 需要同时透传多个 v-model

Vue 3 支持多个 v-model,显式写法更灵活:

<template>
  <CustomForm 
    v-model:firstName="firstName"
    v-model:lastName="lastName"
    v-model:email="email"
  />
</template>

<!-- CustomForm 组件内部 -->
<template>
  <div>
    <Input 
      :modelValue="firstName"
      @update:modelValue="$emit('update:firstName', $event)"
    />
    <Input 
      :modelValue="lastName"
      @update:modelValue="$emit('update:lastName', $event)"
    />
  </div>
</template>

3. 需要在更新时添加额外逻辑

<template>
  <BaseInput 
    :modelValue="modelValue"
    @update:modelValue="handleUpdate"
  />
</template>

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

const handleUpdate = (val) => {
  // 添加验证、格式化等额外逻辑
  const formatted = val.trim().toLowerCase()
  emit('update:modelValue', formatted)
}
</script>

什么时候用哪种写法?

场景推荐写法原因
简单透传v-model简洁清晰
需要验证/转换显式展开便于插入逻辑
多个 v-model显式展开更明确每个绑定
教学示例显式展开展示底层机制
与 $attrs 混用显式展开避免冲突,更可控

实际项目中的写法

在 <script setup> 中,如果想在 JavaScript 逻辑中访问 props,必须显式接收。

代码优化:以一个编辑器(EasyMDE)组件为例,实现其在Vue不同版本下(父子组件数据双向绑定)的最佳实践(附:防抖 Debounce 优化)

最推荐:使用 defineModel


示例中显式写出 :modelValue + @update:modelValue 是为了让代码更教学化和透明化,实际开发中可以根据场景灵活选择。


defineModel() 是 Vue 3.4+ 引入的新特性,让 v-model 的双向绑定变得更加简洁。


官网内容
defineModel()

这个宏可以用来声明一个双向绑定 prop,通过父组件的 v-model 来使用。组件 v-model 指南中也讨论了示例用法。


在底层,这个宏声明了一个 model prop 和一个相应的值更新事件。如果第一个参数是一个字符串字面量,它将被用作 prop 名称;否则,prop 名称将默认为 "modelValue"。


在这两种情况下,你都可以再传递一个额外的对象,它可以包含 prop 的选项和 model ref 的值转换选项。


// 声明 "modelValue" prop,由父组件通过 v-model 使用
const model = defineModel()
// 或者:声明带选项的 "modelValue" prop
const model = defineModel({ type: String })

// 在被修改时,触发 "update:modelValue" 事件
model.value = "hello"

// 声明 "count" prop,由父组件通过 v-model:count 使用
const count = defineModel("count")
// 或者:声明带选项的 "count" prop
const count = defineModel("count", { type: Number, default: 0 })

function inc() {
  // 在被修改时,触发 "update:count" 事件
  count.value++
}

完整功能对比

特性传统写法defineModel()
代码量多(需手动声明 props/emits)少(自动生成)
类型安全需手动定义类型自动推导,支持泛型
默认值props 中定义通过参数配置
修饰符手动处理自动支持 .trim 等
可读性较复杂简洁直观

版本要求

版本defineModel() 支持
Vue 3.3 及以下❌ 不支持
Vue 3.4✅ 正式引入(实验性)
Vue 3.5+✅ 稳定支持

总结:defineModel() 是 Vue 3.4 的重大改进,大幅简化了 v-model 的使用,让代码更简洁、类型更安全。如果你的项目使用 Vue 3.4+,强烈推荐使用这个新特性!

更多推荐