Vue 2 / Vue 3 中 v-model 的区别
v-model 这题特别容易被答浅。很多人只会说:
- Vue 2 是
value + input - Vue 3 是
modelValue + update:modelValue
这句话没错,但不够。更好的答法应该覆盖:
- 原生表单上的
v-model本质是什么 - 组件上的
v-model协议怎么变了 - 多个
v-model、.sync、修饰符在 Vue 3 里怎么统一
面试速答(30 秒版 TL;DR)
v-model本质一直没变,都是 “传值 + 监听更新事件” 的语法糖。- Vue 2 和 Vue 3 的主要区别集中在 组件上的
v-model协议:- Vue 2 默认是
valueprop +input事件 - Vue 3 默认是
modelValueprop +update:modelValue事件
- Vue 2 默认是
- Vue 3 还新增了:
v-model:xxx,支持多个双向绑定- 用
v-model:arg统一替代很多原来.sync的场景
- 如果面试官继续追问,可以补一句:
- Vue 3.4+ 还提供了
defineModel宏,简化子组件内部写法,但底层协议没有变。
- Vue 3.4+ 还提供了
一、先讲本质:v-model 从来都不是魔法
无论 Vue 2 还是 Vue 3,v-model 本质都是语法糖。
1. 原生表单上的语法糖
例如:
<input v-model="keyword" />
本质接近于:
<input :value="keyword" @input="keyword = $event.target.value" />
不同表单控件会略有差异:
- 文本框通常基于
value+input - 复选框、单选框、
select的取值和事件时机会不同
但核心思想始终是:
父状态下发,输入事件上报,再回写状态。
所以你回答 v-model,先把“语法糖”四个字说出来,基本不会错。
二、Vue 2 组件上的 v-model
Vue 2 在自定义组件上,默认协议是:
- prop:
value - event:
input
1. 父组件写法
<MyInput v-model="title" />
2. 子组件等价写法
<script>
export default {
props: {
value: String,
},
methods: {
onInput(e) {
this.$emit('input', e.target.value)
},
},
}
</script>
也就是说,Vue 2 的 v-model 约定很强:默认就是 value/input。
3. Vue 2 的自定义 model
Vue 2 还允许你通过 model 选项改默认协议:
model: {
prop: 'checked',
event: 'change',
}
但这套写法的问题是:
- 心智负担较高
- 不够统一
- 多个双向绑定场景不好表达
三、Vue 3 组件上的 v-model
Vue 3 把默认协议统一改成:
- prop:
modelValue - event:
update:modelValue
1. 父组件写法
<MyInput v-model="title" />
2. 子组件写法
<script setup lang="ts">
const props = defineProps<{
modelValue: string
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
function onInput(e: Event) {
emit('update:modelValue', (e.target as HTMLInputElement).value)
}
</script>
这套命名的优势在于:
- 一眼就知道这是双向绑定协议
update:xxx很容易推广到多个字段- 能和组件事件命名体系统一
四、Vue 3 最大升级:支持多个 v-model
这是非常高频的追问点。
Vue 2 默认只能有一个“主 v-model 协议”,多字段双绑通常要靠:
.sync- 自定义 prop + 自定义事件
Vue 3 直接支持:
<UserForm v-model:name="name" v-model:age="age" />
对应的子组件协议是:
name+update:nameage+update:age
这背后的意义不是“语法新奇”,而是:
- 双向绑定从“一个特殊协议”变成了“统一命名规则”
- 组件 API 设计更清晰
五、.sync 为什么在 Vue 3 里弱化了
Vue 2 里你经常会看到:
<Dialog :visible.sync="visible" />
本质是:
- 传
visible - 监听
update:visible
Vue 3 的思路是把这种模式统一收敛到:
<Dialog v-model:visible="visible" />
所以更稳的答法是:
Vue 3 不是简单删除
.sync,而是把“父子双向同步”统一进了v-model:arg这一套协议里。
六、原生表单上的 v-model 有变化吗
面试里这是个细节点。
更准确的说法是:
- 原生表单上的基本心智模型没有本质变化
- 变化主要发生在组件协议层
- Vue 3 在编译和统一规则上更规范,但不是把所有表单绑定原理推翻重来
所以如果题目是“Vue 2 / Vue 3 中 v-model 的区别”,重点还是答组件层。
七、Vue 3.4+ 的 defineModel 是什么关系
这是新一点的追问,答出来会加分。
例如:
const model = defineModel<string>()
它的作用是:
- 简化子组件里
props + emits的模板代码 - 让默认
v-model写法更直接
但要强调:
- 它是 编译期语法糖
- 底层协议仍然是
modelValue + update:modelValue
八、典型题 & 标准答法
Q1:Vue 2 和 Vue 3 的 v-model 差异是什么?
答:本质都还是“传值 + 更新事件”的语法糖,最大差异在组件默认协议。Vue 2 是 value + input,Vue 3 是 modelValue + update:modelValue。此外 Vue 3 支持 v-model:xxx 多模型绑定,并把很多原来 .sync 的场景统一收敛进这套命名规则。
Q2:为什么 Vue 3 要改成 modelValue 和 update:modelValue?
答:为了统一和泛化。value/input 更像某个特定输入控件的默认叫法,而 modelValue 和 update:xxx 更适合组件层的通用协议,也能自然扩展到多个双向绑定字段。
Q3:v-model 是不是真的“双向绑定”?
答:从使用体验看是双向绑定,但从实现看,它本质仍是单向下发数据、通过事件上报变更,再由父组件决定是否回写。所以它不是打破单向数据流,而是对“prop + emit”做了语法糖封装。
九、易错点 / 坑
- 只记住名字变化,忘了说“本质是语法糖”。
- 以为 Vue 3 的
v-model和.sync完全无关。实际上很多.sync场景就是被统一进v-model:arg。 - 把原生表单和组件协议混着讲,导致答案没有重点。
- 误以为
defineModel改变了底层协议。它只是简化写法。
速记要点
- 本质不变:
v-model = prop + event - Vue 2:
value + input - Vue 3:
modelValue + update:modelValue - Vue 3 支持多个
v-model .sync语义被统一到v-model:arg