v-model 的本质
Vue3 中的 v-model 本质上是一个语法糖,它展开后包含两个部分:
• 一个
modelValue
的 prop• 一个
update:modelValue
的事件
<!-- 使用 v-model -->
<ChildComponent v-model="searchText" />
<!-- 等价于 -->
<ChildComponent
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
自定义 v-model 名称
Vue3 允许我们自定义 v-model 的名称,使用方式如下:
<!-- 父组件 -->
<ChildComponent v-model:visible="isVisible" />
<!-- 等价于 -->
<ChildComponent
:visible="isVisible"
@update:visible="newValue => isVisible = newValue"
/>
多个 v-model
Vue3 支持在同一个组件上使用多个 v-model:
<!-- 父组件 -->
<UserDialog
v-model:visible="isVisible"
v-model:username="username"
v-model:age="age"
/>
Dialog 组件封装
完整的 v-model 实现示例
<template>
<el-dialog
v-model="dialogVisible"
title="新增类别"
width="400px"
destroy-on-close
center
@close="closeDialog"
>
<el-form :model="form" :rules="rules" ref="formRef">
<el-form-item label="类别名称" :label-width="formLabelWidth" prop="categoryName">
<el-input v-model="form.categoryName" placeholder="请输入类别名称" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="confirmAddCategory">确认</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, defineProps, defineEmits } from 'vue'
import type { FormInstance } from 'element-plus'
const props = defineProps({
visible: {
type: Boolean,
required: true
},
formLabelWidth: {
type: String,
default: '80px'
}
})
const emits = defineEmits(['update:visible', 'addCategory'])
// 使用计算属性来同步对话框的显示状态
const dialogVisible = computed({
get() {
return props.visible
},
set(value) {
emits('update:visible', value)
}
})
const formRef = ref<FormInstance>()
const form = ref({
categoryName: ''
})
const rules = {
categoryName: [
{ required: true, message: '请输入类别名称', trigger: 'blur' },
{ min: 2, max: 50, message: '类别名称长度在 2 到 50 个字符', trigger: 'blur' }
]
}
const closeDialog = () => {
dialogVisible.value = false
form.value.categoryName = ''
formRef.value?.resetFields()
}
const confirmAddCategory = () => {
if (!formRef.value) return
formRef.value.validate((valid: boolean) => {
if (valid) {
emits('addCategory', form.value.categoryName.trim())
closeDialog()
}
})
}
</script>
<style scoped>
.dialog-footer {
padding-transform: translateY( 20px;
}
</style>
核心知识点详解
computed 实现双向绑定
// 1. 基础版本
const dialogVisible = computed({
get() {
return props.visible
},
set(value) {
emits('update:visible', value)
}
})
// 2. 带类型的完整版本
const dialogVisible = computed<boolean>({
get() {
return props.visible
},
set(value: boolean) {
emits('update:visible', value)
}
})
TypeScript 类型支持
// 1. Props 类型定义
interface Props {
visible: boolean
modelValue?: string
}
// 2. Emits 类型定义
interface Emits {
(e: 'update:visible', value: boolean): void
(e: 'update:modelValue', value: string): void
(e: 'close'): void
}
// 3. 使用类型
const props = withDefaults(defineProps<Props>(), {
modelValue: ''
})
const emits = defineEmits<Emits>()
常见使用场景
<!-- 1. 基础用法 -->
<CategoryDialog v-model:visible="isVisible" />
<!-- 2. 带默认值的使用 -->
<CategoryDialog
v-model:visible="isVisible"
:model-value="defaultCategory"
/>
<!-- 3. 多个 v-model 的使用 -->
<CategoryDialog
v-model:visible="isVisible"
v-model:category-name="categoryName"
v-model:category-type="categoryType"
/>
最佳实践和注意事项
Props 默认值设置
// 使用 withDefaults 设置默认值
const props = withDefaults(defineProps<Props>(), {
visible: false,
modelValue: '',
width: '400px'
})
状态重置处理
const resetState = () => {
// 1. 重置表单
formRef.value?.resetFields()
// 2. 重置本地状态
form.value = {
categoryName: ''
}
// 3. 通知父组件
emits('update:modelValue', '')
}
// 在关闭时调用
const handleClose = () => {
resetState()
emits('close')
}
组件封装技巧
// 1. 使用 defineExpose 暴露方法
const open = () => {
dialogVisible.value = true
}
const close = () => {
dialogVisible.value = false
}
defineExpose({
open,
close
})
// 2. 使用方式
const dialogRef = ref()
dialogRef.value?.open()
使用示例
<!-- 父组件 -->
<template>
<div>
<el-button @click="openDialog">打开对话框</el-button>
<CategoryDialog
v-model:visible="dialogVisible"
@addCategory="handleAddCategory"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const dialogVisible = ref(false)
const openDialog = () => {
dialogVisible.value = true
}
const handleAddCategory = (category: string) => {
console.log('新增类别:', category)
dialogVisible.value = false
}
</script>