Vue3的v-model在组件上的自定义实现
理解Vue3中v-model的指令如何工作是实现自定义组件双向绑定的基础。本质上,v-model是一个语法糖,它会为组件绑定一个名为 modelValue 的 prop 和一个名为 update:modelValue 的事件。在Vue3中,这一机制得到了增强,允许你在一个组件上使用多个v-model绑定,并且可以自定义每个绑定的名称。
理解组件 v-model 的工作原理
当在父组件中对一个子组件使用 v-model 时:
<ChildComponent v-model="parentData" />
Vue 会在编译时将其转换为:
<ChildComponent
:modelValue="parentData"
@update:modelValue="newValue => parentData = newValue"
/>
子组件需要通过 modelValue 这个 prop 来接收父组件的数据,并通过触发 update:modelValue 事件来将新值传回给父组件。
基础实现:自定义单个 v-model
目标:创建一个名为 MyInput 的输入框组件,使其支持 v-model。
第一步:定义子组件的 props 和 emits
在子组件的 <script setup> 中,使用 defineProps 和 defineEmits 宏来声明接收的属性和可以触发的事件。
<!-- MyInput.vue -->
<script setup>
const props = defineProps({
// 接收父组件通过 v-model 传递的值
modelValue: {
type: String,
required: true
}
})
const emit = defineEmits([
// 声明组件可以触发的更新事件
'update:modelValue'
])
</script>
第二步:在模板中使用 props 并触发事件
在子组件的模板中,将接收到的 modelValue 展示出来,并在输入框的值发生变化时,通过触发 update:modelValue 事件来通知父组件更新数据。
<!-- MyInput.vue -->
<template>
<input
type="text"
:value="props.modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
第三步:在父组件中使用
父组件现在可以直接对 MyInput 使用 v-model,就像使用原生 HTML 元素一样。
<!-- Parent.vue -->
<template>
<div>
<p>父组件中的数据:{{ parentMessage }}</p>
<MyInput v-model="parentMessage" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import MyInput from './MyInput.vue'
const parentMessage = ref('Hello')
</script>
进阶实现:多字段绑定 (v-model:参数)
Vue3允许在同一个组件上使用多个 v-model 绑定,通过 v-model:参数名 的语法实现。这极大地增强了组件的灵活性。
目标:创建一个 UserProfileForm 组件,它同时需要用户的“名字”和“邮箱”两个数据。
第一步:定义带有具体名称的 props 和 emits
子组件需要为每个要双向绑定的数据定义一个具体的 prop 名称和对应的更新事件。
<!-- UserProfileForm.vue -->
<script setup>
const props = defineProps({
// 接收名为 `name` 的绑定
name: {
type: String,
required: true
},
// 接收名为 `email` 的绑定
email: {
type: String,
required: true
}
})
const emit = defineEmits([
// 声明对应的更新事件
'update:name',
'update:email'
])
</script>
第二步:在模板中分别绑定并触发事件
在模板中,为每个表单控件绑定对应的 prop,并在其值变化时触发正确的更新事件。
<!-- UserProfileForm.vue -->
<template>
<div>
<label>
名字:
<input
type="text"
:value="props.name"
@input="emit('update:name', $event.target.value)"
/>
</label>
<br>
<label>
邮箱:
<input
type="email"
:value="props.email"
@input="emit('update:email', $event.target.value)"
/>
</label>
</div>
</template>
第三步:在父组件中使用多个 v-model 绑定
父组件使用 v-model:name 和 v-model:email 分别绑定两个不同的响应式数据。
<!-- Parent.vue -->
<template>
<div>
<UserProfileForm
v-model:name="userName"
v-model:email="userEmail"
/>
<div>
<p>父组件显示:</p>
<p>名字:{{ userName }}</p>
<p>邮箱:{{ userEmail }}</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import UserProfileForm from './UserProfileForm.vue'
const userName = ref('Alice')
const userEmail = ref('alice@example.com')
</script>
使用计算属性优化实现
在上面的实现中,模板里的事件处理略显冗长。我们可以利用 Vue 的计算属性(computed)来使代码更清晰,尤其是在处理复杂的转换逻辑时。
改写子组件
使用 computed 的 getter 和 setter 来封装数据的读取和更新逻辑。
<!-- UserProfileForm.vue (优化版) -->
<script setup>
import { computed } from 'vue'
const props = defineProps({
name: String,
email: String
})
const emit = defineEmits(['update:name', 'update:email'])
// 使用计算属性封装 `name` 的双向绑定
const nameValue = computed({
// getter: 返回父组件传递过来的 prop 值
get() {
return props.name
},
// setter: 当子组件内部修改此值时,触发事件通知父组件
set(newValue) {
emit('update:name', newValue)
}
})
// 同理封装 `email` 的双向绑定
const emailValue = computed({
get() {
return props.email
},
set(newValue) {
emit('update:email', newValue)
}
})
</script>
模板可以简化为直接绑定这些计算属性。
<!-- UserProfileForm.vue (优化版) -->
<template>
<div>
<label>
名字:
<input type="text" v-model="nameValue" />
</label>
<br>
<label>
邮箱:
<input type="email" v-model="emailValue" />
</label>
</div>
</template>
Vue3 与 Vue2 的 v-model API 对比
了解新旧 API 的差异有助于迁移旧代码或理解技术选型。
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 默认 Prop | value |
modelValue |
| 默认事件 | input |
update:modelValue |
| 多个绑定 | 不支持(需用 .sync 修饰符) |
支持 (v-model:title, v-model:author) |
| 自定义修饰符 | 不支持(仅内置 .lazy等) |
支持(通过 v-model:capital 并处理 modelModifiers prop) |
| 组件内定义 | 使用 model 选项 |
直接使用 props 和 emits |
通过遵循以上步骤,你就可以在Vue3中轻松地为自定义组件实现灵活、强大的 v-model 双向数据绑定了。

暂无评论,快来抢沙发吧!