Vue中的v-model
Vue 中 v-model 的本质、常见用法与组件封装中的双向绑定边界。
#tech / dev / frame
#resource / vue3
#type / concept
#status / growing
[!info] related notes
- 所属 MOC: Vue MOC
- 上位主题: vue3
- 相关概念: Vue中的props和emit
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对应 propfoo和事件update:foo,不是modelValue。 - 在组件上使用 v-model 不等于”组件可以随便改数据”:子组件仍然只是发出变更请求,父组件决定是否接受。
面试要点
来自 vue-v-model-interview-question 的面试视角整理。
一句话回答
v-model 是”值绑定 + 更新通知”的语法糖,本质仍然是基于 props + emit 的一条约定通道。
最稳的回答主线
- 表单元素上:绑定值并监听输入变化
- 组件上:本质仍然是父组件持有状态、子组件发出更新事件
- Vue 3 支持多个命名 v-model 和自定义修饰符
- 它只是把常见写法缩短了,不是神秘的直接双向改值