Vue中的v-model

Vue 中 v-model 的本质、常见用法与组件封装中的双向绑定边界。

#tech / dev / frame #resource / vue3 #type / concept #status / growing

[!info] related notes

Vue中的v-model

一句话定义

v-model 是”值绑定 + 更新通知”的语法糖,本质仍然基于 props + emit 的单向数据流。

核心机制

原生表单元素上的 v-model

<template>
  <!-- 等价于 :value="text" @input="text = $event.target.value" -->
  <input v-model="text" />

  <!-- 复选框:绑定布尔值 -->
  <input type="checkbox" v-model="checked" />

  <!-- 单选框 -->
  <input type="radio" v-model="picked" value="a" />
  <input type="radio" v-model="picked" value="b" />

  <!-- 下拉选择 -->
  <select v-model="selected">
    <option value="a">A</option>
    <option value="b">B</option>
  </select>
</template>

<script setup>
import { ref } from 'vue'
const text = ref('')
const checked = ref(false)
const picked = ref('a')
const selected = ref('a')
</script>

不同表单元素绑定的事件不同:

  • <input> / <select>input 事件
  • <input type="checkbox"> / <input type="radio">change 事件

组件上的 v-model

Vue 3 中,组件上的 v-model 等价于:

<!-- 父组件 -->
<MyInput v-model="name" />

<!-- 等价于 -->
<MyInput :modelValue="name" @update:modelValue="name = $event" />

子组件的实现:

<!-- MyInput.vue -->
<script setup>
const props = defineProps({
  modelValue: String
})
const emit = defineEmits(['update:modelValue'])
</script>

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

或者用 defineModel(Vue 3.4+)简化:

<script setup>
const model = defineModel()
</script>

<template>
  <input :value="model" @input="model = $event.target.value" />
</template>

多个 v-model(Vue 3.3+)

<!-- 父组件 -->
<UserForm
  v-model:firstName="first"
  v-model:lastName="last"
/>

<!-- 子组件 UserForm.vue -->
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input v-model="firstName" />
  <input v-model="lastName" />
</template>

每个命名 v-model 对应一个独立的 prop + emit 通道。

自定义修饰符

<!-- 父组件:使用自定义修饰符 -->
<MyInput v-model.trim.uppercase="text" />

<!-- 子组件:接收修饰符 -->
<script setup>
const model = defineModel({
  set(value) {
    if (model.value.modifiers.trim) value = value.trim()
    if (model.value.modifiers.uppercase) value = value.toUpperCase()
    return value
  }
})
</script>

Vue 3.4+ 的 defineModel 原生支持修饰符,通过 model.modifiers 访问。

内置修饰符

<!-- .lazy:把 input 事件改为 change 事件 -->
<input v-model.lazy="text" />

<!-- .number:自动转为数字类型 -->
<input v-model.number="age" />

<!-- .trim:自动去除首尾空格 -->
<input v-model.trim="text" />

边界与常见误解

  • v-model 不是直接双向改值:父组件持有状态,子组件通过 emit 通知变更,本质仍是单向数据流。
  • v-model 不应该替代所有 props/emit:复杂组件应该用显式的 props + emit,接口更清晰。
  • .number 修饰符在 <input type="text"> 上的行为:如果输入无法被 parseFloat 解析,返回原始字符串而非 NaN。
  • v-model 在自定义组件上默认 prop 名是 modelValue:Vue 2 中是 value,Vue 3 改为 modelValue
  • 多个 v-model 的命名v-model:foo 对应 prop foo 和事件 update:foo,不是 modelValue
  • 在组件上使用 v-model 不等于”组件可以随便改数据”:子组件仍然只是发出变更请求,父组件决定是否接受。

面试要点

来自 vue-v-model-interview-question 的面试视角整理。

一句话回答

v-model 是”值绑定 + 更新通知”的语法糖,本质仍然是基于 props + emit 的一条约定通道。

最稳的回答主线

  • 表单元素上:绑定值并监听输入变化
  • 组件上:本质仍然是父组件持有状态、子组件发出更新事件
  • Vue 3 支持多个命名 v-model 和自定义修饰符
  • 它只是把常见写法缩短了,不是神秘的直接双向改值
创建于 2026/3/19 更新于 2026/5/27