面试官推了推眼镜:“我们项目用的是 Vue,对吧?”
“是的,我们主要用 Vue 3。”
“那我问个简单点的,
v-model
是怎么实现的?”“呃,它是语法糖吧……就是帮我们绑定
value
和监听input
……”“那你知道 Vue 3 的
v-model
和 Vue 2 有什么不同?它做了什么?组件里又该怎么用?”你愣了一下,心里咯噔:这事儿我还真没深究过……
很多人对 v-model
的印象还停留在“它帮我们双向绑定”,但到底怎么帮的、帮了哪些事、Vue 3 做了哪些变化、为什么要变化,真正能讲清楚的人并不多。
今天我们就深入拆解一下,一行 v-model
背后到底发生了什么事。
🕰️ Vue 2:v-model 的“前世”
先来看 Vue 2:
<Child v-model="form.name" />
这个语法糖等价于:
<Child :value="form.name" @input="val => form.name = val" />
你看到这里可能会点点头:“对,就是绑定 value
,然后监听 input
事件。”
确实没错,但:
组件里怎么接这个
value
和input
?
在 Vue 2 的组件里这么写:
// Child.vue
export default {
props: ['value'],
methods: {
updateValue(newVal) {
this.$emit('input', newVal)
}
}
}
组件内部要接收一个叫 value
的 prop,并触发一个叫 input
的事件。
这种方式虽然约定俗成,但也有明显的缺陷:
value
容易和其他 prop 名冲突- 所有 v-model 都只能绑定一个字段
- 不支持多个 v-model(比如分别绑定
title
和content
)
于是,Vue 3 来了。
🌱 Vue 3:v-model 的“今生”
在 Vue 3 中,还是这行代码:
<Child v-model="form.name" />
它被编译成了下面这样:
<Child modelValue="form.name" @update:modelValue="val => form.name = val" />
这就是 Vue 3 对 v-model
的标准行为:
- 传值:通过
modelValue
prop - 回传:通过
update:modelValue
事件
对应组件内部这样写:
// Child.vue
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function update(val) {
emit('update:modelValue', val)
}
优势是啥?
- 组件更好维护,避免了
value
的语义冲突 - 支持多个
v-model
绑定 - 结构更清晰,统一使用
modelValue
开头
Vue 3 用 modelValue
替代 value
,避免了跟表单原生属性冲突,增强组件语义的明确性。具名 v-model
使得多字段双向绑定更灵活。
小Tip
直接通过 modelValue
这个 prop 绑定,不用 v-model:
<Child :modelValue="xxx" @update:modelValue="yyy" />
但应该没人会这么干,毕竟v-model
这么简洁好用。
🧩 进阶 v-model:多个绑定 + 自定义命名
接下来我们来看看,当你需要更灵活地绑定多个属性或命名时,Vue 3 又是如何优雅应对的。
Vue 3 为组件通信带来了更多灵活性,其中一个重要升级就是:支持多个 v-model
和 自定义绑定的 prop 名称。
多个字段:一个组件绑定多个 prop
比如我们有个弹窗组件,既要绑定标题,又要绑定内容:
<Modal v-model:title="form.title" v-model:content="form.content" />
这行代码最终会被编译成:
<Modal
:title="form.title"
:content="form.content"
@update:title="val => form.title = val"
@update:content="val => form.content = val"
/>
在子组件内部:
// Modal.vue
defineProps(['title', 'content'])
const emit = defineEmits(['update:title', 'update:content'])
function updateTitle(val) {
emit('update:title', val)
}
function updateContent(val) {
emit('update:content', val)
}
这是不是有点 React “受控组件” 的味道?
多个字段独立传参、独立更新,逻辑更加清晰、组件更易维护,相比 Vue 2 的单字段绑定,灵活度提升明显。
自定义名称:不再被 modelValue
限制
你可能还想问:
v-model
一定只能绑定到modelValue
吗?我能不能自定义名字?
当然可以。
Vue 3 的做法是使用具名 v-model
替代 Vue 2 中的 model
配置项,不再推荐如下写法:
// Vue 2 时代的写法
export default {
model: {
prop: 'checked',
event: 'change'
},
props: ['checked'],
emits: ['change']
}
现在我们直接用具名绑定:
<MySwitch v-model:checked="isOn" />
组件内部写法:
defineProps(['checked'])
const emit = defineEmits(['update:checked'])
function toggle() {
emit('update:checked', !props.checked)
}
绑定字段名和事件名一目了然,不局限于 modelValue
,也无需写死绑定逻辑。
✨ Vue 3.3 新特性:defineModel
语法糖
Vue 3.3 新增了 defineModel
,主要解决了手动声明 modelValue
prop 的冗余和类型声明繁琐问题。它让响应式状态声明更简洁,尤其对 TypeScript 友好。
它的用法很简单,帮你自动声明了 modelValue
(或者你指定的其他名称)的 prop,且支持类型推断。
示例:
<script setup lang="ts">
const model = defineModel({
name: 'modelValue',
type: String
})
const emit = defineEmits(['update:modelValue'])
function onInput(e: Event) {
const target = e.target as HTMLInputElement
emit('update:modelValue', target.value)
}
</script>
<template>
<input :value="model" @input="onInput" />
</template>
这里:
defineModel
自动帮你定义了modelValue
这个 prop- 变量
model
就是传入的值 - 你仍然需要手动用
emit('update:modelValue', value)
来通知父组件
🧩 原理图解:这样理解最简单
可以把 v-model
看作是下面这个转换器:
[v-model="xxx"]
===>
:propName="xxx"
@update:propName="val => xxx = val"
默认情况下,propName = modelValue
如果使用具名绑定 v-model:title
,那 propName = title
只要记住这几步步,你就能秒懂任何形式的 v-model
!
⚠️ 需要注意的小细节
1. 在组件中使用 v-model
,需要手动 emit
如果你只是传了 modelValue
,却忘了触发 update:modelValue
,那页面是不会更新的:
function onInput(e) {
emit('update:modelValue', e.target.value)
}
Vue 没有帮你做双向绑定,它只是做了“语法转译”。
2. 不要在组件中直接修改 modelValue
// 错误写法
props.modelValue = 'newValue' // Vue 会报警告
要改只能 emit,保持数据流是从父向子传,子通过事件通知父。
🛠️ Vue 2 vs Vue 3 一眼对比
对比点 | Vue 2 | Vue 3 |
---|---|---|
默认 prop 名 | value | modelValue |
默认事件名 | input | update:modelValue |
多个 v-model | 不支持 | ✅ 支持具名 v-model:xxx |
自定义字段名 | 用 model 配置项 | 直接具名 v-model:xxx |
类型推断 | 手动声明,类型弱 | ✅ 支持 defineModel 语法糖 |
数据更新方式 | 手动 emit | 同样手动 emit,无自动双绑 |
🎯 小总结
v-model
看似一行代码,背后其实做了三件事:
- 将
v-model="x"
转为:modelValue="x"
; - 添加
@update:modelValue
监听; - 要求子组件手动
emit
更新。
Vue 3 把 “value + input” 升级为更语义化的 “modelValue + update:modelValue”,不仅避免了命名冲突,还支持多个字段绑定和自定义名称。 但需要注意:
它只是“语法糖”,真正的双向更新还是得你在组件里手动触发事件。
掌握这几点,面试官再问你 v-model
,你就能从容不迫地答出底层原理!
如果你觉得这篇文章对你有帮助,欢迎点赞 👍、收藏 ⭐、评论 💬 让我知道你在看! 后续我也会持续输出更多 前端打怪笔记系列文章,敬请期待!❤️