1. 基本用法回顾
v-model 是 :value + @input 的语法糖:
vue
<!-- 等价于 -->
<input v-model="msg" />
<input :value="msg" @input="msg = $event.target.value" />
在组件上,v-model 默认绑定 value prop 和 input 事件。
2. 处理不同数据结构
2.1 基本类型(String / Number / Boolean)
vue
<template>
<input v-model="name" />
<input v-model.number="age" type="number" />
<input v-model.trim="name" />
<input v-model.lazy="name" />
</template>
<script>
export default {
data() {
return {
name: '',
age: 0
}
}
}
</script>
修饰符:
.number--- 自动转为数值.trim--- 自动去除首尾空格.lazy--- 在change事件而非input事件触发更新
2.2 对象(Object)
方式一:直接绑定整个对象(不推荐,难维护)
vue
<user-form v-model="user"></user-form>
方式二:拆开绑定(推荐)
vue
<!-- 父组件 -->
<user-form
:name.sync="user.name"
:email.sync="user.email"
/>
<!-- UserForm 组件内部 -->
<script>
export default {
props: ['name', 'email'],
methods: {
updateName(val) {
this.$emit('update:name', val)
}
}
}
</script>
方式三:自定义 input 事件携带整个对象
vue
<!-- 子组件 -->
<script>
export default {
props: ['value'],
methods: {
updateField(key, val) {
const newObj = { ...this.value, [key]: val }
this.$emit('input', newObj)
}
}
}
</script>
2.3 数组(Array)
v-model 不能直接绑定数组的「某一项」并自动触发更新,需要借助额外技巧。
问题:直接绑定数组项无效
vue
<!-- ❌ 这样写不会触发视图更新 -->
<div v-for="(item, index) in list" :key="index">
<input v-model="list[index]" />
</div>
解法一:用 this.$set(Vue2 响应式坑)
vue
<script>
export default {
data() {
return {
list: []
}
},
mounted() {
// ✅ 正确方式:确保响应式
this.$set(this.list, 0, 'hello')
}
}
</script>
解法二:用对象数组 + 绑定对象属性
vue
<template>
<div v-for="(item, index) in list" :key="index">
<!-- ✅ 绑定对象的属性,Vue2 可检测 -->
<input v-model="item.text" />
<input type="checkbox" v-model="item.done" />
</div>
</template>
<script>
export default {
data() {
return {
list: [
{ text: '学习 Vue', done: false },
{ text: '写笔记', done: true }
]
}
}
}
</script>
解法三:自定义组件封装数组项
vue
<!-- ArrayItem.vue -->
<script>
export default {
props: ['value'],
render(h) {
return h('input', {
domProps: { value: this.value },
on: {
input: (e) => this.$emit('input', e.target.value)
}
})
}
}
</script>
<!-- 父组件 -->
<array-item v-for="(item, i) in list" :key="i"
:value="list[i]"
@input="val => $set(list, i, val)"
/>
2.4 嵌套对象(Deep Nested Object)
深层嵌套对象使用 v-model 时,需确保每一层都是响应式的。
vue
<script>
export default {
data() {
return {
form: {
user: {
profile: {
name: '',
avatar: ''
}
}
}
}
},
methods: {
// ✅ 使用 $set 确保嵌套属性响应式
initForm() {
this.$set(this.form.user, 'profile', {
name: 'Weiming',
avatar: ''
})
}
}
}
</script>
<template>
<div>
<input v-model="form.user.profile.name" />
<input v-model="form.user.profile.avatar" />
</div>
</template>
注意:Vue2 的响应式系统无法检测:
- 直接给对象新增属性(用
this.$set(obj, key, val))- 直接通过索引修改数组项(用
this.$set(arr, index, val))
3. Vue2 特有的 .sync 修饰符
Vue2 中,除了 v-model,还可以用 .sync 实现「多个 prop 双向绑定」:
vue
<!-- 父组件 -->
<my-component
:title.sync="pageTitle"
:content.sync="pageContent"
/>
<!-- 子组件 -->
<script>
export default {
props: ['title', 'content'],
methods: {
updateTitle(newTitle) {
this.$emit('update:title', newTitle)
}
}
}
</script>
.sync在 Vue3 中被移除,改用v-model:title语法。
4. 自定义组件中的 v-model(多数据类型)
4.1 自定义 prop 和事件名
vue
<script>
export default {
model: {
prop: 'checked', // 代替默认的 value
event: 'change' // 代替默认的 input
},
props: ['checked'],
methods: {
toggle() {
this.$emit('change', !this.checked)
}
}
}
</script>
4.2 复选框组(数组类型)
vue
<!-- CheckboxGroup.vue -->
<script>
export default {
props: ['value'],
methods: {
toggle(option) {
const newValue = this.value.includes(option)
? this.value.filter(v => v !== option)
: [...this.value, option]
this.$emit('input', newValue)
}
}
}
</script>
<!-- 使用 -->
<checkbox-group v-model="selectedFruits" />
5. 常见坑与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 数组项修改不触发视图更新 | Vue2 无法检测索引赋值 | this.$set(arr, index, val) |
| 对象新增属性无响应 | Vue2 无法检测新增属性 | this.$set(obj, key, val) |
v-model 绑定原始数组项 |
原始值无引用,无法响应式 | 改用对象数组 |
| 多个值需要双向绑定 | v-model 只能绑定一个 |
使用 .sync 修饰符 |
| 深度嵌套对象修改无响应 | 嵌套层级过深 | 用 this.$set 或 JSON.parse(JSON.stringify()) 强制刷新 |
6. 最佳实践总结
- 简单场景 :直接用
v-model - 对象/表单场景 :拆成多个
.sync,或封装成自定义组件 - 数组场景:用对象数组,避免直接绑定原始值
- 深层嵌套:初始化时把结构写完整,避免动态新增属性
- Vue2 → Vue3 迁移 :提前用
v-model:propName替代.sync的写法