第 33 题:Vue3 v-model 原理(语法糖 → props + emit → modelValue → update:modelValue)

184 阅读2分钟

第 32 题:Vue3 v-model 原理(语法糖 → props + emit → modelValue → update:modelValue)

回答结构:
核心回答 → 双向绑定机制 → 多个 v-model → 自定义组件实现 → 源码解析 → 面试官追问 → 高分总结


🎯 一、核心回答(面试官最关注)

Vue3 中 v-model 本质是:

props + emits 的语法糖
默认使用:

  • prop:modelValue
  • event:update:modelValue

你写:

<MyInput v-model="msg" />

Vue 会转换成:

<MyInput 
  :modelValue="msg" 
  @update:modelValue="msg = $event" 
/>

也就是说:

  • 父组件通过 modelValue 把值传给子组件
  • 子组件通过 emit("update:modelValue", newValue) 把值传回父组件

🎯 二、v-model 的完整数据流(非常重要)

父组件数据(msg)
  ↓ 作为 prop 传递
modelValue
  ↓ 子组件修改后 emit
update:modelValue
  ↓
父组件接收 $event → 更新 msg

这就是 Vue3 的单向数据流 + 事件回传。


🎯 三、子组件必须如何写?

标准写法:

<script setup>
const props = defineProps({
  modelValue: String
})

const emit = defineEmits(["update:modelValue"])

function update(e) {
  emit("update:modelValue", e.target.value)
}
</script>

<template>
  <input :value="props.modelValue" @input="update" />
</template>

核心:emit 必须叫 update:modelValue


🎯 四、多个 v-model 原理(Vue3 强化的功能)

你可以写:

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

Vue 会转成:

<MyRange
  :start="start"
  :end="end"
  @update:start="start = $event"
  @update:end="end = $event"
/>

子组件写法:

const props = defineProps(["start", "end"])

const emit = defineEmits(["update:start", "update:end"])

emit("update:start", newStart)
emit("update:end", newEnd)

Vue2 完全不支持多 v-model
这是 Vue3 的重大升级。


🎯 五、v-model 修饰符原理(.trim、.number、自定义修饰符)

例如:

<MyInput v-model.trim="msg" />

会编译成:

<MyInput
  :modelValue="msg"
  @update:modelValue="msg = $event.trim()"
/>

自定义修饰符:

<MyInput v-model.capitalize="msg" />

Vue 会给 props 添加:

modelModifiers: { capitalize: true }

子组件可读取:

if (props.modelModifiers.capitalize) {
  value = value.toUpperCase()
}

🎯 六、源码级解析(重点是 compile 阶段)

Vue 在模板编译时会把:

v-model="xxx"

转换成:

{
  props: [
    createObjectProperty("modelValue", xxx)
  ],
  events: [
    createObjectProperty("onUpdate:modelValue", 
        createAssignmentExpression(xxx, $event)
    )
  ]
}

也就是说:

  • 编译阶段完全变成 props + emit
  • 运行时不需要任何特殊处理

🎯 七、面试官追问(附高分回答)


❓1:Vue3 为什么把 v-model 的字段改成 modelValue?

高分回答:

  • 统一所有组件的输入 prop(没有 value/input 混乱)
  • 多 v-model 必须有字段名
  • modelValue 更标准化

Vue2:

  • value
  • input
  • checked
  • change

非常零散。

Vue3 彻底统一。


❓2:为什么 update:modelValue 这么设计?

因为:

  • Vue3 的事件是通过字符串绑定
  • 冒号语法能清晰表示“更新某个 prop”
  • 可以支持多个 v-model

这是经典的可扩展性设计。


❓3:多个 v-model 怎么区分?

靠:

v-model:title
v-model:desc
v-model:start
v-model:end

字段名前缀。

编译后变成:

:modelValue:title
@update:modelValue@update:title

❓4:什么时候用 watch 代替 v-model?

回答:

  • 当你需要监听变化但不希望自动更新值
  • 当你希望多个 props 组合成一个值

否则用 v-model 更简洁。


🎯 八、金牌总结(背下来即可)

Vue3 的 v-model 是 modelValue + update:modelValue 的语法糖,
通过 props 单向流动传值,通过 emit 进行反向更新,实现了可控的双向绑定。
支持多个 v-model,自定义修饰符,编译阶段将 v-model 转换为标准化的 props + emit 结构,是 Vue3 数据流设计中的关键部分。