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:
html
<!-- 这两种写法完全等价 -->
<BaseInput v-model="username" />
<BaseInput :modelValue="username" @update:modelValue="val => username = val" />
为什么示例中显式写出展开形式?
1. 展示透传机制
html
<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 简写:
html
<!-- 看起来简洁,但不够直观 -->
<BaseInput v-bind="$attrs" v-model="modelValue" />
2. 需要同时透传多个 v-model
Vue 3 支持多个 v-model,显式写法更灵活:
html
<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. 需要在更新时添加额外逻辑
html
<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 的双向绑定变得更加简洁。
官网内容
这个宏可以用来声明一个双向绑定 prop,通过父组件的
v-model来使用。组件 v-model 指南中也讨论了示例用法。
在底层,这个宏声明了一个 model prop 和一个相应的值更新事件。如果第一个参数是一个字符串字面量,它将被用作 prop 名称;否则,prop 名称将默认为
"modelValue"。
在这两种情况下,你都可以再传递一个额外的对象,它可以包含 prop 的选项和 model ref 的值转换选项。
javascript// 声明 "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+,强烈推荐使用这个新特性!