🔗 Vue 3 defineModel API
🔗 传统双向绑定的困境
在Vue3之前的组件开发中,实现父子组件双向绑定通常需要:
- 使用
v-model
配合组件内部的modelValue
prop和update:modelValue
事件 - 多个双向绑定时需要使用
.sync
修饰符 - 需要手动处理props和emit事件
这种模式在复杂组件中会导致大量样板代码,特别是处理多个双向绑定时尤为明显。
🔗 defineModel的横空出世
Vue3.3推出的defineModel API带来了三大核心优势:
- 声明式双向绑定:自动处理prop和事件
- 类型安全:完美支持TypeScript类型推导
- 多模型支持:轻松管理多个双向绑定状态
🔗 基础用法示例
vue
<!-- CustomInput.vue -->
<script setup>
const model = defineModel({
type: String,
required: true
})
</script>
<template>
<input
:value="model"
@input="e => model = e.target.value"
class="custom-input"
>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用组件时完全兼容传统v-model:
vue
<template>
<CustomInput v-model="username" />
</template>
1
2
3
2
3
🔗 案例:多功能颜色选择器
让我们通过一个颜色选择器组件演示defineModel的高级用法:
使用示例:
vue
<template>
<ColorPicker
v-model:color="primaryColor"
v-model:alpha="opacity"
/>
</template>
1
2
3
4
5
6
2
3
4
5
6
🔗 案例:智能开关组件
🔗 传统实现方式对比
🔗 常规写法(props/emit)
vue
<script setup>
const props = defineProps({
modelValue: { type: Boolean, required: true }
})
const emit = defineEmits(['update:modelValue'])
const toggle = () => {
emit('update:modelValue', !props.modelValue)
}
</script>
<template>
<div
class="switch"
:class="{ active: modelValue }"
@click="toggle"
>
<div class="thumb" />
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
🔗 defineModel写法
vue
<script setup>
const model = defineModel({
type: Boolean,
required: true
})
const toggle = () => {
model.value = !model.value
}
</script>
<template>
<div
class="switch"
:class="{ active: model }"
@click="toggle"
>
<div class="thumb" />
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
🔗 核心优势体现
- 直观的状态操作:直接操作model.value而不是通过emit
- 减少代码量:省略props/emit声明(减少40%代码)
- 类型安全:自动推导boolean类型
🔗 案例:带状态管理的评分组件
vue
<script setup>
// 主评分模型
const rating = defineModel({
type: Number,
validator: value => value >= 0 && value <= 5
})
// 临时hover状态(不触发更新)
const tempRating = ref(0)
const setRating = (value) => {
rating.value = value
}
const setTemp = (value) => {
tempRating.value = value
}
</script>
<template>
<div class="rating-wrapper">
<div
v-for="star in 5"
:key="star"
class="star"
:class="{
active: star <= (tempRating || rating),
highlighted: star <= tempRating
}"
@mouseover="setTemp(star)"
@mouseleave="setTemp(0)"
@click="setRating(star)"
>
★
</div>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
🔗 使用示例
vue
<template>
<!-- 双向绑定 -->
<StarRating v-model="userRating" />
<!-- 只读模式 -->
<StarRating :modelValue="averageRating" />
</template>
1
2
3
4
5
6
7
2
3
4
5
6
7
🔗 进阶用法:带转换器的搜索框
vue
<script setup>
const searchModel = defineModel({
// 自动去除首尾空格
set(value) {
return value.trim()
},
// 空值时显示默认提示
get(value) {
return value || 'Type to search...'
}
})
const onBlur = () => {
if (searchModel.value === '') {
searchModel.value = 'Type to search...'
}
}
</script>
<template>
<input
type="text"
v-model="searchModel"
:class="{ placeholder: searchModel === 'Type to search...' }"
@focus="searchModel = ''"
@blur="onBlur"
>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
🔗 何时应该使用defineModel?
🔗 更适合以下场景
- ✅ 需要维护组件内部状态和外部同步
- ✅ 需要处理多个双向绑定参数
- ✅ 需要类型安全的表单组件
- ✅ 需要自定义值转换逻辑
- ✅ 需要简化复杂组件的状态管理
🔗 与传统方式的性能对比
指标 | defineModel | 传统方式 |
---|---|---|
类型推导 | ✅ | ❌ |
多模型支持 | ✅ | 需要.sync |
值转换器 | 内置 | 支持手动实现 |
组件复用性 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
🔗 最佳实践建议
🔗 命名规范
vue
// 推荐
v-model:primaryColor
v-model:secondaryColor
// 不推荐
v-model:color1
v-model:color2
1
2
3
4
5
6
7
2
3
4
5
6
7
🔗 类型声明
ts
// 明确类型声明
defineModel<ColorType>()
1
2
2
🔗 复杂校验
ts
defineModel({
validator: (value) => {
return HEX_COLOR_REGEX.test(value) ||
RGB_COLOR_REGEX.test(value)
}
})
1
2
3
4
5
6
2
3
4
5
6
🔗 组合式API整合
ts
const model = defineModel()
watch(model, (newVal) => {
// 自动响应变化
})
const computedModel = computed({
get: () => model.value,
set: (val) => {
model.value = processValue(val)
}
})
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
🔗 典型应用场景
🔗 表单增强组件
- 带校验的输入框
- 复合日期选择器
- 文件上传组件
🔗 UI控件库
- 滑动条组件
- 开关切换组件
- 评分组件
🔗 复杂交互组件
- 颜色选择器
- 可视化编辑器
- 实时过滤器组件
🔗 总结升华
defineModel的真正价值体现在:
- 开发效率:减少样板代码,专注业务逻辑
- 维护成本:显式声明提升可读性
- 类型安全:与TypeScript深度集成
- 模式统一:规范组件间的数据流管理
通过这个优化后的评分组件案例,我们可以清晰看到defineModel如何:
- 简化父子组件通信
- 保持状态同步的自然性
- 提升代码的可维护性
- 增强类型系统的支持
这才是Vue3组合式API的精髓所在——用更声明式的方式表达组件逻辑,同时保持响应式的核心优势。