🌟 前言:为什么要关注 defineModel
?
过去我们在 Vue 组件中使用 v-model
时,常需要这样写:
// 子组件
defineProps(['modelValue'])
defineEmits(['update:modelValue'])function update(val) {emit('update:modelValue', val)
}
冗长、重复、繁琐,尤其在 TypeScript 中还不好维护。
自 Vue 3.4 起,defineModel()
这个宏让我们可以一行代码完成 v-model 的全部功能,甚至可以更强大地支持多个 v-model、修饰符、类型推导和默认值设置。
📦 一、基础用法详解
✅ 1. 声明默认 v-model(绑定 modelValue
)
// 子组件
const model = defineModel()
对应父组件:
<Child v-model="myValue" />
等价于原来的 props 和 emits:
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])watch(model, val => emit('update:modelValue', val))
✅ 2. 使用自定义 v-model 名称(比如 v-model:count)
const count = defineModel('count')
父组件这样用:
<Counter v-model:count="num" />
等价于:
defineProps(['count'])
defineEmits(['update:count'])
🎯 二、进阶用法:类型、默认值、必填
✅ 1. 指定类型(TypeScript 支持)
const username = defineModel<string>()
默认是 Ref<string | undefined>
,如果希望一定有值:
const username = defineModel<string>({ required: true })
// Ref<string>
✅ 2. 设置默认值(但注意同步问题)
const age = defineModel<number>({ default: 18 })
🚨 注意: 父组件未传值时,子组件会用默认值,但父组件的值为 undefined
,二者不同步!
解决方案:
// 父组件初始化 ref 值
const myAge = ref(18)
🧩 三、多个 v-model 的支持(多个 defineModel)
Vue 3.4 支持多个 v-model
:
const checked = defineModel<boolean>()
const label = defineModel<string>('label')
对应父组件:
<Checkbox v-model="isChecked" v-model:label="labelText" />
这对于封装复选框、选择器、弹窗特别实用。
🪄 四、修饰符与转换器:.trim / .number / .uppercase
✅ 结构出修饰符
const [value, modifiers] = defineModel<string, 'trim'>()
父组件写:
<MyInput v-model.trim="inputValue" />
✅ 使用转换器处理值
const [value, modifiers] = defineModel<string, 'trim'>({set(val) {return modifiers.trim ? val.trim() : val}
})
也可以加自定义转换规则:
const [value, modifiers] = defineModel<string, 'uppercase'>({get(val) {return modifiers.uppercase ? val.toUpperCase() : val}
})
🔨 五、实战场景:封装可复用输入组件
<!-- BaseInput.vue -->
<script setup lang="ts">
const [value, modifiers] = defineModel<string, 'trim'>({set(val) {return modifiers.trim ? val.trim() : val}
})
</script><template><input :value="value" @input="e => (value.value = e.target.value)" />
</template>
父组件中:
<BaseInput v-model.trim="username" />
这样封装的组件自动具备:
v-model
双向绑定.trim
修饰符支持- 类型提示完善
🧠 六、常见坑位与注意事项
场景 | 问题 | 解决方案 |
---|---|---|
使用 default | 父组件值未同步 | 父组件初始化 ref 值 |
defineModel 无法识别泛型 | 使用泛型时记得标注类型 <T> | defineModel<string>() |
旧项目报错 | 低版本 Vue 不支持 | 升级到 Vue 3.4+ |
修饰符未生效 | 没有解构 modifiers | const [v, modifiers] = defineModel() |
🧭 七、最佳实践总结
✅ 推荐场景:
- 封装输入类组件:Input、Select、DatePicker
- 封装复杂交互组件:弹窗、表格编辑单元
- 封装通用控件库,提升可维护性
🚫 不推荐:
- 低版本 Vue 项目(3.4 以下)
- 和 setup 外逻辑混用(defineModel 仅支持
<script setup>
)
📘 附:对比 defineModel 与传统写法
项目 | defineModel | 传统写法 |
---|---|---|
写法简洁 | ✅ 一行定义 | ❌ 需 props + emits |
多个 v-model | ✅ 多次声明 | ❌ 手动处理 |
类型提示 | ✅ 泛型支持 | ❌ 需额外定义 props 类型 |
默认值支持 | ✅ 支持 default | ✅ 但仍需 emit |
修饰符 | ✅ 自动支持 | ❌ 需手动处理 |
🧪 结语:如何开始使用?
- Vue 升级到
3.4+
:
npm install vue@^3.4
-
创建组件,使用
<script setup>
和defineModel
-
标准化写法,配合类型提示、修饰符使用,大幅提升组件封装质量和开发效率