前言
在一次面试的时候,面试官让我说说vue3的v-model。我说现在vue3.4提供了一个defineModel编译宏,通过这个实现自定义组件的双向绑定,但当我说到这个defineModel以后他表示很困惑,他说有这个吗?....然后赶紧讲了之前vue3怎么声明v-model的方式。 不过写这篇也是为了加深这块的记忆,以及defineModel具体的实现原理。
defineModel()
Vue 3.4 引入的 defineModel 是一个编译宏(Compiler Macro),其核心原理是通过编译时的代码转换,简化组件间双向数据绑定的实现。它是对传统 v-model 用法的封装,自动处理了 props 和事件触发的逻辑。
核心原理分步解析:
-
编译时转换:
-
当你使用
defineModel()时,Vue 编译器会在构建阶段将其转换为标准的props和emit代码。 -
输入代码(开发者编写的):
javascript const model = defineModel() -
编译后代码(实际运行的):
javascript const model = computed({ get() { return props.modelValue // 来自父组件的 prop }, set(value) { emit('update:modelValue', value) // 触发父组件更新 } })
-
-
双向绑定自动化:
- 父组件通过
v-model传递数据时,defineModel自动生成对应的modelValueprop。 - 子组件修改
model.value时,自动触发update:modelValue事件,无需手动emit。
- 父组件通过
-
支持多模型和配置:
-
可通过参数定义多个模型,如
defineModel('title')会生成titleprop 和update:title事件。 -
允许配置默认值、校验规则等:
javascript const model = defineModel({ default: '', validator: (v) => v.length > 0 })
-
-
底层响应式依赖:
- 返回的
model是一个 Computed Ref,依赖 Vue 的响应式系统。修改model.value会触发父组件的重新渲染。
- 返回的
与传统方式的对比:
传统写法(手动处理 prop + emit):
javascript
// 子组件
props: ['modelValue'],
emits: ['update:modelValue'],
setup(props, { emit }) {
const value = computed({
get: () => props.modelValue,
set: (v) => emit('update:modelValue', v)
})
return { value }
}
// setup写法
const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);
const count = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
使用 defineModel:
javascript
// 子组件
const model = defineModel() // 一行替代所有样板代码
优点总结:
- 代码简洁:减少手动编写 prop/emit 的重复代码。
- 类型安全:在 TypeScript 中自动推断类型,提升开发体验。
- 可维护性:集中管理模型逻辑(默认值、校验等)。
- 兼容性:支持 Vue 3.4+ 的 Options API 和 Composition API。
注意事项:
- 需使用
@vue/compiler-sfc5.0+ 版本(与 Vue 3.4 配套)。 - 在 JSX/TSX 中需通过插件支持。
- 底层依然是标准的
props+emit,无运行时性能损耗。
通过 defineModel,Vue 将双向绑定的实现细节隐藏在了编译层,让开发者更专注于业务逻辑。