v-model 的参数
默认情况下,组件的 v-model 绑定的是 modelValue prop 和 update:modelValue 事件,但可通过“参数”自定义绑定的 prop 名称,实现更灵活的双向绑定(如多值绑定)。
带参数的 v-model 使用
父组件中,通过 v-model:参数名 指定绑定的 prop 名称:
<!-- 父组件:绑定到子组件的 title prop -->
<MyComponent v-model:title="bookTitle" />其中 bookTitle 是父组件的响应式变量,title 是子组件中自定义的 prop 名称。
子组件适配参数
子组件中,给 defineModel() 传递第一个参数(字符串),指定与参数对应的 prop 名称:
<!-- MyComponent.vue -->
<script setup>
// 声明接收 "title" 参数的模型
const title = defineModel('title')
</script>
<template>
<!-- 输入框与 title 绑定,间接与父组件的 bookTitle 同步 -->
<input type="text" v-model="title" />
</template>此时,父组件的 v-model:title="bookTitle" 会被编译为:
<MyComponent
:title="bookTitle" <!-- 传给子组件的 title prop -->
@update:title="$event => (bookTitle = $event)" <!-- 监听 update:title 事件 -->
/>若需同时配置 prop 选项(如必填性),在参数后传递选项对象:
// 声明 title 参数为必填
const title = defineModel('title', { required: true })多个 v-model 绑定
结合 v-model 的参数,可在单个组件上绑定多个 v-model,实现多组数据的双向同步。每个 v-model 对应子组件中一个独立的 defineModel() 声明。
父组件使用多个 v-model
父组件通过不同的参数,绑定多个值到子组件:
<!-- 父组件:分别绑定 first 和 last 到子组件的 first-name 和 last-name -->
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>其中 first 和 last 是父组件中两个独立的响应式变量。
子组件实现多个模型
子组件中,通过 defineModel(参数名) 分别声明对应的模型:
<!-- UserName.vue -->
<script setup>
// 声明 first-name 参数的模型
const firstName = defineModel('firstName')
// 声明 last-name 参数的模型
const lastName = defineModel('lastName')
</script>
<template>
<!-- 两个输入框分别绑定两个模型 -->
<input type="text" v-model="firstName" placeholder="First name" />
<input type="text" v-model="lastName" placeholder="Last name" />
</template>此时:
- 第一个输入框的变化会通过 firstName 同步到父组件的 first;
- 第二个输入框的变化会通过 lastName 同步到父组件的 last;
- 父组件修改 first 或 last 时,对应的输入框也会同步更新。
处理 v-model 修饰符
与原生 input 的 v-model 类似,组件的 v-model 也支持修饰符(如内置的 .trim,或自定义修饰符)。子组件可通过解构 defineModel() 的返回值,获取修饰符并根据修饰符处理值。
基本用法:获取修饰符
父组件给 v-model 添加修饰符(如自定义的 .capitalize,表示首字母大写):
<!-- 父组件:添加 .capitalize 修饰符 -->
<MyComponent v-model.capitalize="myText" />子组件中,通过 [model, modifiers] = defineModel() 解构出修饰符对象(modifiers 是一个对象,键为修饰符名,值为 true):
<!-- MyComponent.vue -->
<script setup>
// 解构出模型和修饰符
const [model, modifiers] = defineModel()
console.log(modifiers) // { capitalize: true }(父组件添加了该修饰符时)
</script>
<template>
<input type="text" v-model="model" />
</template>根据修饰符处理值
通过给 defineModel() 传递 get 和 set 选项,可基于修饰符对值进行加工:
- set(value):当子组件修改模型值时触发,接收原始值,返回处理后的值(会同步到父组件);
- get(value):当父组件修改值同步到子组件时触发,接收父组件的原始值,返回处理后的值(供子组件使用)。
例如,实现 .capitalize 修饰符(将输入值的首字母转为大写):
<script setup>
const [model, modifiers] = defineModel({
set(value) {
// 若存在 capitalize 修饰符,处理值
if (modifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value // 无修饰符时直接返回原始值
}
})
</script>
<template>
<input type="text" v-model="model" />
</template>此时:
- 用户在输入框输入“hello”,set 方法会将其转为“Hello”,并同步到父组件的 myText;
- 若父组件直接修改 myText 为“world”,子组件的输入框会显示“World”(因 set 会处理所有值的修改)。
带参数的 v-model 修饰符
若结合参数使用修饰符(如多个 v-model 各有不同修饰符),子组件需为每个模型单独解构修饰符:
<!-- 父组件:给两个 v-model 分别添加修饰符 -->
<UserName
v-model:first-name.capitalize="first"
v-model:last-name.uppercase="last"
/>子组件中,每个 defineModel(参数名) 都可解构出对应的修饰符:
<!-- UserName.vue -->
<script setup>
// 解构 first-name 的模型和修饰符
const [firstName, firstNameModifiers] = defineModel('firstName')
// 解构 last-name 的模型和修饰符
const [lastName, lastNameModifiers] = defineModel('lastName')
console.log(firstNameModifiers) // { capitalize: true }
console.log(lastNameModifiers) // { uppercase: true }
</script>
<template>
<input v-model="firstName" placeholder="First name" />
<input v-model="lastName" placeholder="Last name" />
</template>后续可针对 firstNameModifiers 和 lastNameModifiers 分别实现修饰符逻辑(如 capitalize 首字母大写、uppercase 全大写)。
总结
组件上的 v-model 是“prop + 事件”的语法糖,核心是实现父子组件的双向数据同步。Vue 3.4+ 的 defineModel() 宏大幅简化了实现流程,无需手动声明 prop 和事件;同时支持参数(多 v-model 绑定)和修饰符(值处理),满足复杂场景需求。
- 基础用法:子组件 defineModel() 声明模型,父组件 v-model="值" 绑定;
- 底层机制:基于 modelValue prop 和 update:modelValue 事件;
- 参数:通过 v-model:参数名 自定义绑定的 prop,子组件 defineModel(参数名) 适配;
- 修饰符:解构 defineModel() 返回的 modifiers,通过 get/set 选项处理值。
