【vue3】v-model 的 “新玩法“
前言
Vue3.6
已经进入 Alpha 阶段,Vue3.4 早在 2023 年 12 月就把 defineModel
转正式了!
defineModel
是 Vue 3.3+ 中引入的一个编译器宏(Compile-time Macro),用于简化组件中 v-model
的双向绑定实现。它解决了传统 v-model
实现中需要手动声明 props 和 emit 的繁琐问题,让代码更简洁。
defineModel宏简化了这一过程。它会返回一个ref,这个ref可以像普通的数据ref一样使用,但会自动处理prop和事件。
自动实现 v-model
的双向绑定:
声明一个响应式的 ref;自动注册对应的 prop(如 modelValue
);自动注册对应的事件(如 update:modelValue
)
defineModel 是什么
一句话定义:让子组件像原生 <input>
一样直接支持 v-model
的语法糖; 说白了就是一个 宏(macro),在编译期把 defineModel()
展开成 props
+ emit
特性:
1. 自动连接prop和事件:内部自动声明了`modelValue` prop和`update:modelValue`事件。
2. 支持修饰符:可以处理`v-model`的修饰符(如`.trim`, `.number`)。
如果父组件使用修饰符,例如`v-model.trim`,那么修饰符会以对象的形式传递给`defineModel`的第二个参数。我们可以根据修饰符对值进行处理。
3. 支持多个`v-model`:通过指定参数,如`defineModel('foo')`,可以用于多个`v-model`绑定(例如`v-model:foo`)。
宏 VS 函数
宏:编译期代码生成,运行时
0
额外开销。函数:运行时真实调用。
因此defineModel
不需要import
,也不能在普通<script>
或.js/.ts
文件里使用。// 书写 const model = defineModel<string>({ default: 'hello' })// 编译后 const props = defineProps({modelValue: { type: String, default: 'hello' } }) const emit = defineEmits(['update:modelValue']) const model = computed({get: () => props.modelValue,set: val => emit('update:modelValue', val) })
注意:基于
<script setup>
,可直接复制到*.vue
文件运行。
传统实现 vs defineModel
方式 | 传统实现 | defineModel |
---|---|---|
代码量 | 需要手动声明 props + emit + 事件处理 | 一行声明 |
数据更新 | 需调用 emit('update:modelValue', value) | 直接给 ref 赋值 |
修饰符 | 需通过 props.modelModifiers 手动处理 | 在 setter 中自动处理 |
类型安全 | 需额外类型声明 | 支持泛型类型 |
单 v-model
父组件
<template><UserName v-model="name" /><p>父组件拿到的值:{{ name }}</p>
</template><script setup lang="ts">
import { ref } from 'vue'
import UserName from './Names.vue'const name = ref('名称')
</script>
子组件 Names.vue
<template><input v-model="modelValue" />
</template><script setup lang="ts">
const modelValue = defineModel<string>()
// 等价于 const modelValue = defineModel<string>({ required: true })
</script>
多个 v-model
父组件
<template><UserFormv-model:name="form.name"v-model:age="form.age"v-model:phone="form.phone"/><pre>{{ form }}</pre>
</template><script setup lang="ts">
import { reactive } from 'vue'
import UserForm from './UserForm.vue'const form = reactive({name: '张三',age: 18,phone: '13800138000'
})
</script>
子组件
<template><input v-model="name" placeholder="姓名" /><input v-model="age" placeholder="年龄" /><input v-model="phone" placeholder="手机号" />
</template><script setup lang="ts">
const name = defineModel<string>('name')
const age = defineModel<number>('age')
const phone = defineModel<string>('phone')
</script>
带修饰符 & 转换器
不用手动 .trim
父组件
<template><TrimInput v-model.trim="keyword" /><p>父组件值:{{ keyword }}</p>
</template><script setup lang="ts">
import { ref } from 'vue'
import TrimInput from './TrimInput.vue'const keyword = ref('')
</script>
子组件TrimInput.vue
<template><input v-model="modelValue" />
</template><script setup lang="ts">
const [modelValue, modifiers] = defineModel<string, 'trim'>()// 当父组件写 v-model.trim 时,modifiers.trim === true
if (modifiers.trim) {// 通过 set 函数实时转换
}
</script>
实时转换,用 get / set
:
const [modelValue, modifiers] = defineModel<string, 'trim'>({set(val) {return modifiers.trim ? val.trim() : val}
})
TypeScript 高阶
需求 | 写法 |
---|---|
必填 | defineModel<string>({ required: true }) |
可选+默认值 | defineModel<string>({ default: '张三' }) |
联合类型 | defineModel<'male' | 'female'>() |
复杂对象 | defineModel<User>() |
注意:默认值如果是对象
/数组
,请用函数返回新实例,避免引用共享:
defineModel<string[]>({ default: () => ['A', 'B'] })
注意事项
版本要求:Vue ≥ 3.3,且需配置构建工具:
// vite.config.js
export default {plugins: [vue({script: {defineModel: true // 启用宏}})]
}
本质是语法糖:编译后会展开为:
// defineModel() 编译结果
const modelValue = defineModel()
// ↓ 编译后 ↓
const modelValue = ref(props.modelValue)
watch(modelValue, (v) => emit('update:modelValue', v))
修饰符处理:当父组件使用 v-model.trim
时,子组件可通过 defineModel
的 set
选项处理。
总结
defineModel
是 Vue 组件双向绑定的趋势,它:
✅ 减少样板代码(无需手动处理 props/emit)
✅ 提供更直观的响应式体验
✅ 完美支持 TypeScript
✅ 简化修饰符处理