🧠 面试官:一行 v-model 背后发生了什么?你真知道吗?

1 阅读5分钟

面试官推了推眼镜:“我们项目用的是 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 事件。”

确实没错,但:

组件里怎么接这个 valueinput

在 Vue 2 的组件里这么写:

// Child.vue
export default {
  props: ['value'],
  methods: {
    updateValue(newVal) {
      this.$emit('input', newVal)
    }
  }
}

组件内部要接收一个叫 value 的 prop,并触发一个叫 input 的事件。

这种方式虽然约定俗成,但也有明显的缺陷:

  • value 容易和其他 prop 名冲突
  • 所有 v-model 都只能绑定一个字段
  • 不支持多个 v-model(比如分别绑定 titlecontent

于是,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"

111.png

默认情况下,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 2Vue 3
默认 prop 名valuemodelValue
默认事件名inputupdate:modelValue
多个 v-model不支持✅ 支持具名 v-model:xxx
自定义字段名model 配置项直接具名 v-model:xxx
类型推断手动声明,类型弱✅ 支持 defineModel 语法糖
数据更新方式手动 emit同样手动 emit,无自动双绑

🎯 小总结

v-model 看似一行代码,背后其实做了三件事:

  1. v-model="x" 转为 :modelValue="x"
  2. 添加 @update:modelValue 监听;
  3. 要求子组件手动 emit 更新。

Vue 3 把 “value + input” 升级为更语义化的 “modelValue + update:modelValue”,不仅避免了命名冲突,还支持多个字段绑定和自定义名称。 但需要注意:

它只是“语法糖”,真正的双向更新还是得你在组件里手动触发事件。

掌握这几点,面试官再问你 v-model,你就能从容不迫地答出底层原理!

如果你觉得这篇文章对你有帮助,欢迎点赞 👍、收藏 ⭐、评论 💬 让我知道你在看! 后续我也会持续输出更多 前端打怪笔记系列文章,敬请期待!❤️