是的!从 Vue 3.4 开始,Vue 官方引入了 defineModel() 编译宏(macro),极大简化了组件中实现 v-model 双向绑定的写法 ,无需手动声明 props 和 emit,也无需处理 modelValue / update:modelValue 的样板代码。
下面系统讲解 defineModel() 的使用方式、原理、优势和注意事项。
一、基础用法:替代 modelValue + emit
传统写法(Vue 3.0 ~ 3.3)
xml
<!-- Child.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function update() {
emit('update:modelValue', props.modelValue + 1)
}
</script>
<template>
<div>{{ props.modelValue }}</div>
<button @click="update">+</button>
</template>
Vue 3.4+ 新写法: defineModel()
xml
<!-- Child.vue -->
<script setup>
const model = defineModel() // 返回一个 ref
function update() {
model.value++ // 直接修改,自动同步到父组件
}
</script>
<template>
<div>Parent bound v-model is: {{ model }}</div>
<button @click="update">Increment</button>
</template>
父组件完全不变:
xml
<template>
<Child v-model="count" />
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
model 是一个 双向绑定的 ref:
- 读取
model.value→ 获取父组件传入的值 - 修改
model.value→ 自动触发update:modelValue,更新父组件数据
二、支持带参数的 v-model (多模型绑定)
Vue 支持多个 v-model,例如:
xml
<Parent>
<Child v-model:name="name" v-model:age="age" />
</Parent>
使用 defineModel 实现:
xml
<!-- Child.vue -->
<script setup>
const name = defineModel('name')
const age = defineModel('age')
// 或者用对象形式(可选)
// const { name, age } = defineModels({ name: String, age: Number })
</script>
<template>
<input v-model="name" />
<input v-model.number="age" />
</template>
注意:defineModel('propName') 会自动对应 v-model:propName
三、类型与默认值(TypeScript / 运行时校验)
1. 指定类型(TypeScript)
c
const model = defineModel<string>()
// model.value 类型为 string | undefined
2. 设置默认值
arduino
const model = defineModel({ default: 'hello' })
3. 运行时校验 + 默认值
php
const model = defineModel({
type: String,
required: false,
default: 'default text'
})
💡 这些选项会自动转换为等效的 ****props ****声明,由 Vue 编译器处理。
四、与 useAttrs() 协同工作
虽然 defineModel() 自动处理了 modelValue,但其他属性仍需透传:
xml
<script setup>
const model = defineModel()
// 如果有多个根节点,或想控制透传位置,才需要 useAttrs
</script>
<template>
<!-- 单根节点:自动透传 attrs(包括 class/style/@focus 等) -->
<input v-model="model" />
</template>
如果组件有多个根节点 ,必须手动使用 v-bind="$attrs",否则 Vue 会警告。
五、总结:为什么推荐 defineModel() ?
| 对比项 | 传统方式 | defineModel() |
|---|---|---|
| 代码量 | 多(props + emits) | 极简(一行) |
| 易错性 | 容易拼错 update:modelValue |
零错误 |
| 可读性 | 逻辑分散 | 聚焦数据流 |
| 封装效率 | 低 | 高(尤其包装原生元素) |
| TypeScript 支持 | 需手动标注 | 自动推导 |
一句话 :
defineModel() ****让组件的双向绑定回归"直觉"------就像操作本地状态一样简单,却能自动同步到父组件。