Vue3里defineModel的default怎么用?双向绑定默认值这些坑要避开!
不少同学在Vue3项目里用defineModel实现双向绑定时,总会纠结“默认值咋设置?父组件没传值时子组件咋兜底?” 今天就把defineModel和default的用法、坑点、原理一次性讲透,看完自己写组件再也不慌~
先搞懂:defineModel是Vue3的什么新特性?
Vue3.4版本后新增的双向绑定语法糖,核心作用是替代“props接收值 + emit触发更新”的繁琐写法,以前写双向绑定,得这样:
<!-- 旧写法:props+emit -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function handleChange(newVal) {
emit('update:modelValue', newVal)
}
</script>
现在用defineModel,一行代码搞定双向绑定:
<!-- 新写法:defineModel语法糖 -->
<script setup>
const modelValue = defineModel('modelValue')
// 直接修改modelValue,自动触发父组件v-model更新
modelValue.value = '新值'
</script>
编译时,Vue会把defineModel('modelValue')自动转成props: { modelValue } + emit: update:modelValue的组合,帮我们省掉手动写emit的麻烦,开发效率直接起飞~
defineModel的default配置项负责啥?
default是给双向绑定的“模型值”设置初始兜底值——当父组件没通过v-model传值时,子组件用default的值当初始值;父组件传了值,就优先用父组件的。
举个直观例子:子组件想做个“计数器”,父组件没传值时默认显示10,代码这么写:
<!-- 子组件Counter.vue -->
<script setup>
// 给modelValue设置default为10
const modelValue = defineModel('modelValue', { default: 10 })
// 点击事件:修改modelValue,自动触发父组件更新
function handleClick() {
modelValue.value++
}
</script>
<template>
<button @click="handleClick">{{ modelValue }}</button>
</template>
父组件调用时分两种情况:
- 没传
v-model:<Counter />→ 按钮初始显示10,点击后自增(子组件改值,父组件无绑定值,不影响其他逻辑)。 - 传了
v-model:<Counter v-model:modelValue="parentCount" />→ 按钮初始显示parentCount的值,点击后parentCount也会同步更新。
给defineModel加default时,这些细节容易踩坑?
想用好default,得避开3个常见“陷阱”:
父组件传值的优先级更高
default只在父组件没传v-model时生效,如果父组件传了值,哪怕传的是undefined,子组件也会用父组件的值,而非default。
比如子组件写了defineModel('num', { default: 0 }),父组件传<Child v-model:num="undefined" /> → 子组件num初始是undefined,不是0。
响应式要“适配引用类型”
如果default是对象/数组(引用类型),要注意父组件没传值时,子组件修改默认值会影响所有复用该组件的实例(因为引用类型默认值是共享的)。
举个反面例子:
// 错误示范:对象默认值共享风险
const user = defineModel('user', {
default: { name: '默认用户', age: 18 }
})
function changeName() {
user.value.name = '新名字' // 所有没传user的子组件,name都会被改成“新名字”
}
解决办法:用function返回值当default(每次初始化组件时生成新对象):
const user = defineModel('user', {
default: () => ({ name: '默认用户', age: 18 })
})
配合v-model修饰符要“类型匹配”
如果父组件用了v-model修饰符(比如.number .trim),子组件default的类型得和修饰符处理后的值一致,否则容易报错。
比如父组件写<Input v-model:value.number="parentNum" />,子组件default设为字符串:
// 危险:修饰符期望数字,default给字符串
const value = defineModel('value', { default: '100' })
父组件没传时,子组件value初始是字符串'100';父组件传值后变成数字,来回切换容易触发类型错误。建议default类型和修饰符处理后的值保持一致。
defineModel的default和defineProps的default有啥不一样?
很多同学会混淆这俩default,其实核心差异在“单向传值” vs “双向绑定”:
| 对比维度 | defineModel的default | defineProps的default |
|---|---|---|
| 场景 | 双向绑定场景下的“初始兜底值” | 单向传值场景下的“初始兜底值” |
| 值的更新逻辑 | 子组件改值 → 触发父组件v-model更新 | 子组件改props(不推荐)→ 父组件无感知 |
| 编译后本质 | 给隐式props设默认值 + 自动处理emit | 给显式props设默认值 |
举个场景对比:
-
用
defineProps组件”:const props = defineProps({ { default: '默认标题' } }) // 子组件改title → 不触发父组件更新(单向) -
用
defineModel做“可编辑标题”:const title = defineModel('title', { default: '默认标题' }) // 子组件改title → 自动触发父组件v-model更新(双向)
实际项目中,怎么用defineModel的default优化组件设计?
掌握default后,能在这些场景里“偷懒又高效”:
场景1:表单组件的默认占位
做自定义输入框时,父组件没传v-model,输入框显示“请输入内容”:
<!-- 子组件CustomInput.vue -->
<script setup>
const modelValue = defineModel('modelValue', {
default: '请输入内容'
})
function handleInput(e) {
modelValue.value = e.target.value
}
</script>
<template>
<input :value="modelValue" @input="handleInput" />
</template>
父组件调用:
<template> <!-- 没传v-model → 显示“请输入内容” --> <CustomInput /> <!-- 传v-model → 显示parentValue --> <CustomInput v-model:modelValue="parentValue" /> </template>
场景2:弹窗组件的默认可见性
弹窗默认“关闭”,父组件没传v-model时,默认不显示:
<!-- 子组件Dialog.vue -->
<script setup>
const visible = defineModel('visible', {
default: false
})
function openDialog() {
visible.value = true // 打开弹窗,父组件同步更新
}
</script>
<template>
<div v-if="visible" class="dialog">...</div>
<button @click="openDialog">打开弹窗</button>
</template>
父组件想控制弹窗:<Dialog v-model:visible="isShow" /> → 直接通过isShow控制显隐;不想控制就<Dialog /> → 默认关闭,点击按钮打开。
想彻底掌握?得理解defineModel的编译原理!
前面说defineModel是语法糖,那它编译后到底变成啥样?
比如写const count = defineModel('count', { default: 10 }),Vue编译时会做两件事:
- 生成props:
props: { count: { default: 10 } }(所以default本质是给props设默认值)。 - 生成emit逻辑:当修改
count.value时,自动触发emit('update:count', 新值)。
换句话说,defineModel把“props接收 + emit触发更新”的流程全自动托管了,而default就是给这个隐式props设置默认值——和defineProps的props默认值逻辑完全一致,只是场景是双向绑定。
defineModel的default怎么用?
- 作用:父组件没传
v-model时,给双向绑定的“模型值”设初始兜底。 - 坑点:父组件传值优先级更高、引用类型默认值要避免共享、配合修饰符注意类型。
- 原理:编译后等价于“
props默认值 + 自动emit更新”,本质是props默认值的双向场景复用。
下次写组件时,只要涉及双向绑定且需要默认值,直接用defineModel('xxx', { default: 兜底值 }),既简洁又不容易出错~ 要是还碰到奇怪的问题,回到原理想想“编译后是props+emit”,思路瞬间就通啦~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


