← 返回题目列表

Vue3 中 v-model 的原理是什么?和 Vue2 有什么区别?

高频 中等 第 15 / 27 题 更新于 2026/07/29
Vuev-model双向绑定Vue3

简化版

v-model 本质是语法糖:父组件传值给子组件,子组件通过事件通知父组件更新。Vue3 默认使用 modelValueupdate:modelValue,并支持多个 v-model;Vue2 默认是 valueinput

详细版

Vue3 组件上:

<UserInput v-model="name" />

等价于:

<UserInput
  :modelValue="name"
  @update:modelValue="name = $event"
/>

子组件需要声明:

const props = defineProps<{ modelValue: string }>();
const emit = defineEmits<{ 'update:modelValue': [value: string] }>();

Vue3 还支持多个 v-model

<DateRange v-model:start="start" v-model:end="end" />

完整版教学

一、v-model 不是魔法,而是值和事件的约定

很多同学把 v-model 理解成“自动双向绑定”,但面试要说清楚它的展开形式。 父组件把状态传给子组件,子组件不能直接改父组件状态,只能发事件通知父组件更新。 所以它仍然符合单向数据流。

父组件 name
  -> prop 传给子组件
  -> 子组件输入变化 emit
  -> 父组件接收事件更新 name

如果输入了 3 个字符,子组件通常会触发 3 次更新事件。 这解释了为什么复杂输入组件要考虑防抖、校验和更新时机。

二、原生表单上的 v-model 会映射到不同属性和事件

在原生表单元素上,v-model 会根据元素类型选择属性和事件。 文本框通常是 value + input,复选框可能是 checked + change。 这让模板更简洁。

<input v-model="name" />

可以理解成:

<input
  :value="name"
  @input="name = $event.target.value"
/>
元素主要绑定主要事件
text inputvalueinput
checkboxcheckedchange
selectvaluechange

这个表不用死背,重点是知道 v-model 会根据控件类型做展开。

三、Vue3 组件 v-model 默认是 modelValue

Vue3 自定义组件的默认约定是 modelValueupdate:modelValue。 父组件写起来像双向绑定,子组件内部仍然是 props + emit。 这比 Vue2 的 value/input 更统一。

<!-- 父组件 -->
<BaseInput v-model="keyword" />
<!-- 子组件 -->
<script setup lang="ts">
const props = defineProps<{ modelValue: string }>();
const emit = defineEmits<{
  'update:modelValue': [value: string];
}>();
</script>

<template>
  <input
    :value="props.modelValue"
    @input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
  />
</template>

这里子组件没有直接改 props.modelValue。 它只是发出新值,由父组件决定是否接收。 这也是调试自定义表单组件时最关键的一点。

四、Vue3 支持多个 v-model

Vue2 一个组件默认只有一个 v-model,多字段双向绑定常要借助 .sync。 Vue3 支持带参数的多个 v-model。 这对日期范围、坐标选择器、分页组件很常见。

<DateRange v-model:start="startDate" v-model:end="endDate" />

等价于:

<DateRange
  :start="startDate"
  :end="endDate"
  @update:start="startDate = $event"
  @update:end="endDate = $event"
/>

如果一个日期范围组件有 2 个值,Vue3 不需要把它们硬塞进一个对象。 这样字段更明确,父组件也更容易单独监听某个值变化。

五、修饰符会影响更新时机或值处理

v-model 常见修饰符有 .lazy.number.trim。 它们分别影响触发时机、数值转换、去除首尾空格。 自定义组件也可以接收 model modifiers 做定制处理。

<input v-model.trim="keyword" />
<input v-model.number="age" />
<input v-model.lazy="title" />

数字例子:用户输入 "18",普通 v-model 得到字符串 "18";使用 .number 后会尝试转成数字 18。 但转换失败时不应该假设一定是 number,仍要结合表单校验。 修饰符是输入处理辅助,不是完整校验方案。

六、不要在子组件里直接修改 prop

Vue 的单向数据流要求 prop 由父组件拥有。 子组件直接修改 prop 会破坏数据来源,也会触发警告。 正确做法是 emit 更新事件。

// 不推荐
props.modelValue = 'new';

// 推荐
emit('update:modelValue', 'new');

记忆钩子:Vue3 的组件 v-model 记成 modelValue 进来,update:modelValue 出去。

如果子组件需要本地临时编辑状态,可以复制一份内部状态,确认时再 emit。 这在弹窗编辑、复杂表单草稿里很常见。

七、常见误区与追问

  • 误区:v-model 会让子组件直接修改父组件数据。 子组件只是发事件,真正更新发生在父组件表达式里。
  • 误区:Vue3 的 v-model 仍默认是 value/input。 自定义组件默认变成 modelValueupdate:modelValue
  • 误区:多个字段只能传一个对象做 v-model。 Vue3 支持多个带参数的 v-model,如 v-model:startv-model:end
  • 追问:Vue2 的 .sync 在 Vue3 里怎么替代? 通常用带参数的 v-model:propNameupdate:propName 事件替代。
  • 追问:自定义组件如何支持 v-model? 声明对应 prop,触发 update:xxx 事件,父组件用 v-model 接收。
  • 追问:什么时候不适合 v-model? 当子组件只是展示数据、更新逻辑复杂或需要显式命令语义时,用普通 prop 和事件更清楚。

八、加强记忆

这题用“语法糖展开”来回答。原生表单是属性加事件,自定义组件在 Vue3 默认是 modelValueupdate:modelValue;多个字段用 v-model:xxx;最后强调不要直接改 prop,子组件通过 emit 通知父组件更新。