一、defineModel
defineModel
是 Vue 3.4+ 引入的语法糖,用于简化双向绑定(v-model)的实现。它本质上是对 props 和 emit
的封装,使得在组件中使用双向绑定更加简洁和直观。
一、基本用法
父组件
<template>
<ChildComponent v-model="message" />
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello')
</script>
子组件(ChildComponent.vue)
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>
<script setup>
const model = defineModel()
</script>
<template>
<input :value="model" @input="model = $event.target.value" />
</template>
✅ 效果:子组件中的
input
输入值变化会自动同步到父组件的message
上。
二、带参数的用法
你可以为 defineModel
指定参数,控制绑定的属性名和类型:
<script setup>
// 使用自定义 prop 名称和类型
const model = defineModel({ required: true, default: 'default value' })
</script>
或指定非默认的 prop/emit 名称:
const model = defineModel('customValue', { emit: 'update:customValue' })
等价于:
defineProps(['customValue']) defineEmits(['update:customValue'])
三、底层原理
defineModel
实际上是组合了以下两个操作:
- 定义了一个名为
modelValue
的 prop(默认情况下) - 定义了一个名为
update:modelValue
的 emit(用于更新父组件)
因此下面这段代码:
const model = defineModel()
相当于:
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const model = computed({
get() { return props.modelValue },
set(value) { emit('update:modelValue', value) } })
四、适用场景
场景 | 推荐方式 |
---|---|
简单的父子双向绑定 | ✅ defineModel |
多个双向绑定需求 | ❌ 不适合,应使用 defineProps + defineEmits |
自定义 v-model 行为 | ✅ 可通过参数自定义 prop/emit 名称 |
需要复杂验证逻辑 | ✅ 可结合 computed 属性使用 |
五、注意事项
defineModel
只能在<script setup>
中使用- 默认 prop 名为
modelValue
,默认 emit 名为update:modelValue
- 不支持多个 v-model 绑定(如
v-model:title
和v-model:content
同时存在) - 适用于快速开发和简单表单组件封装,不建议在复杂业务逻辑中使用
六、与传统 v-model 的区别
特性 | Vue2 | Vue3(defineModel) |
---|---|---|
v-model 实现方式 | props.value + $emit('input') | props.modelValue + $emit('update:modelValue') |
支持多 v-model | ❌(需手动命名) | ✅(可命名多个) |
语法糖 | ❌ | ✅ defineModel() |
在 <script setup> 中 | ❌ | ✅ 支持 |
七、总结
defineModel
是 Vue3.4+ 提供的一个便捷 API,适合用于快速实现组件间的双向绑定,尤其适用于表单组件封装。虽然简化了代码,但在需要多个双向绑定或复杂逻辑时,仍推荐使用 defineProps
和 defineEmits
显式管理数据流。
二、useModel
useModel
是一个运行时 API(适用于 <script setup>
和 Composition API),用于访问某个特定 v-model
的值。它比 defineModel
更灵活,可以支持非 modelValue
的命名方式(即自定义 v-model:name
)。
示例:
<script setup>
import { useModel } from 'vue'
// 默认使用 modelValue / update:modelValue
const model = useModel('modelValue')
</script>
<template>
<input :value="model" @input="model = $event.target.value" />
</template>
自定义名称(对应 v-model:name
)
// 假设父组件使用 v-model:title="xxx" const title = useModel('title')
此时相当于:
props: ['title'], emits: ['update:title']
一、defineModel
vs useModel
特性 | defineModel | useModel |
---|---|---|
类型推导 | ✅ 完美支持 | ✅ 支持 |
是否是编译器宏 | ✅ 是(仅 <script setup> ) | ❌ 否(可在任何 setup 风格中使用) |
支持自定义名称 | ❌ 否(只能绑定 modelValue ) | ✅ 是 |
返回值类型 | Ref<any> | Ref<any> |
使用场景 | 快速绑定默认 v-model | 处理自定义 v-model:name |
二、注意事项
-
不能同时使用多个
defineModel()
只能有一个默认的v-model
,如果你需要多个双向绑定,请使用useModel('name')
。 -
不建议混合使用 props +
defineModel
因为defineModel
本质上也是基于 props 和emit
实现的,应避免手动操作同名 prop。 -
与 TypeScript 配合良好
const model = defineModel<string>({ default: 'default value' })
三、完整示例
父组件:
<template>
<ChildComponent v-model="text" />
</template>
<script setup>
import { ref } from 'vue' const text = ref('')
</script>
子组件(ChildComponent.vue):
<script setup>
const model = defineModel()
</script>
<template>
<input :value="model" @input="model = $event.target.value" />
</template>
四、总结
功能 | 推荐 API |
---|---|
快速绑定默认 v-model | defineModel() |
绑定自定义 v-model:name | useModel('name') |
获取双向绑定的 ref | ✅ 两者都返回 ref |
支持类型推导(TS) | ✅ 支持 |
运行时灵活性 | useModel 更高 |
编译时优化 | defineModel 更简洁 |
如果你正在使用 Vue 3.4+ 并采用 <script setup>
语法,推荐优先使用 defineModel
来简化 v-model
的处理。对于需要多 v-model
或自定义命名的情况,则使用 useModel
。