在 Vue 3.4 中,defineModel
宏的引入标志着 Vue 双向绑定机制的一次重大革新。作为 Composition API 的重要补充,defineModel
不仅简化了代码结构,还显著提升了开发效率和代码可维护性。本文将深入探讨 defineModel
的核心原理、最佳实践以及在实际项目中的应用场景,展示其如何优雅地解决传统 v-model
实现中的痛点。
在 defineModel
出现之前,Vue 的双向绑定主要依赖于 v-model
和手动管理 props
和 emits
。虽然这些方法有效,但在复杂场景下,代码往往显得冗长且难以维护。
props
和 emits
emits
触发调用,就可以修改父组件的数据了
v-model
还可以借助v-model
,可以省去父组件定义修改数据的方法并传递给子组件这一步
v-model
传递数据给子组件
v-model
默认传递过来的参数名为:modelValue,默认传递过来的事件为:update:modelValuev-model:name
,同理子组件中接受的数据名与事件名改成一致即可尽管 v-model
简化了部分代码,但仍需手动管理 props
和 emits
,尤其是在处理多个双向绑定时,代码复杂度显著增加。所以从 Vue 3.4 开始,官方更加推荐使用 defineModel() 宏来实现双向数据绑定。
defineModel
的诞生:简化双向绑定defineModel 是一个编译器宏,用于在 Vue 组件中定义双向绑定的 prop。它本质上是对 v-model 指令的语法糖,但提供了更简洁、更直观的语法。
父组件还是不变,只需通过v-model
传递数据给子组件即可
通过 defineModel
,子组件无需再显式接收 props
和 emits
,直接通过 defineModel
返回的 ref
对象即可实现双向绑定。
在一些特殊场景下,我们可能还需要使用v-model
的修饰符功能
比如:清除字符串末尾的空格
父组件添加修饰符
子组件获取修饰符
在子组件中,我们可以通过解构 defineModel()
的返回值,来获取父组件添加到子组件 v-model
的修饰符:
// 通过defineModel声明父组件传递过来的数据,返回一个ref对象
const [user, filters] = defineModel({
default: {},
set: (val) => {
console.log('set', val)
}
})
修饰符格式
默认格式为:第一个参数为props值,第二个参数为对应的修饰符(修饰符可能有多个,格式如下)
转换器处理数据
当存在修饰符时,我们可能需要在读取或将其同步回父组件时对其值进行转换。我们可以通过使用 get
和 set
转换器选项来实现这一点:
const [userName, userNameFilters] = defineModel('userName',{
default: '',
set: (val) => {
if(userNameFilters.trim) {
return val.trim()
}
return val
}
})
我们可以在单个组件实例上创建多个v-model
的双向绑定
比如:
子组件同时接受多个v-model
// 通过defineModel声明父组件传递过来的数据,返回一个ref对象
const [user, filters] = defineModel({
default: {},
set: (val) => {
console.log('set', val)
}
})
const [userName, userNameFilters] = defineModel('userName',{
default: '',
set: (val) => {
if(userNameFilters.trim) {
return val.trim()
}
return val
}
})
defineModel
的背后了解了怎么用的,最后再来看看它是怎么实现的
我们知道defineModel
其实就是v-model
的语法糖,所以我们可以对比下两种写法最后的编译结果有什么区别?
不使用defineModel
最终就是props
与emits
分别接收变量与事件
使用defineModel
使用defineModel
后,我们在组件中虽然可以不用像之前那样显式的接收props与emits,但Vue同样会帮我们生成这两块内容,并且可以看到两者红框内基本一样,只不过使用defineModel
会多一个修饰符的接收
defineModel
会被编译成一个 _useModel
方法,这是实现双向绑定的核心。从编译后的代码可以看出,defineModel
会接收父组件传递的 props
和 emits
,并利用 props
中的值进行初始化。当数据需要更新时,它会调用 emits
中注册的事件来通知父组件。然而,在实际开发中,我们通常不会直接操作 props
和 emits
,而是通过 defineModel
返回的 ref
值来直接操作数据。因此,_useModel
的核心任务是确保这个 ref
值与父组件传递的 props
值保持同步,从而实现数据的双向绑定。