跳到主要内容

Vue 2 / Vue 3 中 v-model 的区别

v-model 这题特别容易被答浅。很多人只会说:

  • Vue 2 是 value + input
  • Vue 3 是 modelValue + update:modelValue

这句话没错,但不够。更好的答法应该覆盖:

  1. 原生表单上的 v-model 本质是什么
  2. 组件上的 v-model 协议怎么变了
  3. 多个 v-model.sync、修饰符在 Vue 3 里怎么统一

面试速答(30 秒版 TL;DR)

  • v-model 本质一直没变,都是 “传值 + 监听更新事件” 的语法糖。
  • Vue 2 和 Vue 3 的主要区别集中在 组件上的 v-model 协议
    • Vue 2 默认是 value prop + input 事件
    • Vue 3 默认是 modelValue prop + update:modelValue 事件
  • Vue 3 还新增了:
    • v-model:xxx,支持多个双向绑定
    • v-model:arg 统一替代很多原来 .sync 的场景
  • 如果面试官继续追问,可以补一句:
    • Vue 3.4+ 还提供了 defineModel 宏,简化子组件内部写法,但底层协议没有变。

一、先讲本质: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:name
  • age + 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 要改成 modelValueupdate:modelValue

:为了统一和泛化。value/input 更像某个特定输入控件的默认叫法,而 modelValueupdate: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