Vue3.4新功能:defineModel 让双向绑定更简单
vue 3.4 带来了一个很实用的新功能:defineModel。这个功能让 v-model 双向绑定的代码变得特别简洁。以前需要写很多行的代码,现在一行就能搞定。
以前的 v-model 写法
在 Vue 3.3 及之前的版本中,我们要在子组件里实现 v-model 功能,需要写不少代码。
比如我们要做一个自定义输入框组件:
<!-- CustomInput.vue -->
<script setup>
// 1. 先定义 props
const props = defineProps({
modelValue: {
type: String,
default: ''
}
})
// 2. 再定义 emits
const emit = defineEmits(['update:modelValue'])
// 3. 写事件处理函数
const handleInput = (event) => {
// 4. 手动触发更新
emit('update:modelValue', event.target.value)
}
</script>
<template>
<input
:value="modelValue" <!-- 5. 绑定值 -->
@input="handleInput" <!-- 6. 绑定事件 -->
placeholder="请输入内容"
/>
</template>可以看到,为了实现双向绑定,我们需要完成 6 个步骤。这还只是一个 v-model,如果要支持多个 v-model,代码就更复杂了。
比如用户信息表单组件:
<!-- UserForm.vue -->
<script setup>
// 每个属性都要定义 props
const props = defineProps({
firstName: String,
lastName: String,
email: String
})
// 每个属性都要定义 emits
const emit = defineEmits([
'update:firstName',
'update:lastName',
'update:email'
])
// 每个属性都要写处理函数
const updateFirstName = (e) => emit('update:firstName', e.target.value)
const updateLastName = (e) => emit('update:lastName', e.target.value)
const updateEmail = (e) => emit('update:email', e.target.value)
</script>
<template>
<div class="user-form">
<input :value="firstName" @input="updateFirstName" placeholder="姓" />
<input :value="lastName" @input="updateLastName" placeholder="名" />
<input :value="email" @input="updateEmail" placeholder="邮箱" />
</div>
</template>这种写法不仅麻烦,还容易出错。有时候可能会写错事件名,或者忘记触发更新。
Vue 3.4 的新写法:defineModel
现在有了 defineModel,一切都变得简单了。同样的功能,代码量大大减少。
单个 v-model 的情况:
<!-- CustomInput.vue -->
<script setup>
// 只需要这一行
const modelValue = defineModel()
</script>
<template>
<input
v-model="modelValue" <!-- 直接使用 v-model -->
placeholder="请输入内容"
/>
</template>多个 v-model 的情况:
<!-- UserForm.vue -->
<script setup>
// 每个 v-model 都是一行代码
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
const email = defineModel('email')
</script>
<template>
<div class="user-form">
<input v-model="firstName" placeholder="姓" />
<input v-model="lastName" placeholder="名" />
<input v-model="email" placeholder="邮箱" />
</div>
</template>父组件的用法跟以前完全一样:
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
import UserForm from './UserForm.vue'
const inputValue = ref('初始值')
const firstName = ref('张')
const lastName = ref('三')
const email = ref('zhangsan@example.com')
</script>
<template>
<!-- 单个 v-model -->
<CustomInput v-model="inputValue" />
<p>当前值: {{ inputValue }}</p>
<!-- 多个 v-model -->
<UserForm
v-model:firstName="firstName"
v-model:lastName="lastName"
v-model:email="email"
/>
</template>defineModel 的高级用法
defineModel 不只是简化代码,它还支持一些高级功能。
设置默认值和类型
<script setup>
const title = defineModel({
type: String,
default: '默认标题'
})
const count = defineModel('count', {
type: Number,
default: 0,
required: true
})
</script>自定义修饰符
Vue 的 v-model 支持修饰符,比如 .trim、.number。用 defineModel 也可以处理自定义修饰符。
<script setup>
// 定义带修饰符的 model
const modelValue = defineModel({
set(value) {
// 如果有 uppercase 修饰符,就转成大写
if (modelValue.modifiers.uppercase) {
return value.toUpperCase()
}
return value
}
})
</script>
<template>
<input v-model="modelValue" />
</template>在父组件中使用:
<template>
<!-- 使用自定义修饰符 -->
<CustomInput v-model.uppercase="textValue" />
</template>在组合式函数中使用
defineModel 也可以在组合式函数中使用,这让代码复用更加方便。
<script setup>
// 封装一个带验证的 model
function useValidatedModel(initialValue, validator) {
const model = defineModel({
default: initialValue
})
const error = ref('')
watch(model, (newValue) => {
const result = validator(newValue)
if (result !== true) {
error.value = result
} else {
error.value = ''
}
})
return {
model,
error
}
}
// 使用
const { model: username, error: usernameError } = useValidatedModel('', (value) => {
if (value.length < 3) return '用户名至少3个字符'
return true
})
</script>从旧代码迁移到 defineModel
如果你有现有的项目,想要改用 defineModel,迁移过程很简单。可以一个一个组件慢慢改,不用一次性全部迁移。
迁移步骤:
删除 defineProps 中的 modelValue 定义
删除 defineEmits 中的 update:modelValue 定义
添加 const modelValue = defineModel()
把模板中的 :value="modelValue" @input="handleInput" 改成 v-model="modelValue"
举个例子,把之前的 CustomInput 组件迁移到新写法:
迁移前:
<script setup>
const props = defineProps({
modelValue: String
})
const emit = defineEmits(['update:modelValue'])
const handleInput = (e) => emit('update:modelValue', e.target.value)
</script>
<template>
<input :value="modelValue" @input="handleInput" />
</template>迁移后:
<script setup>
const modelValue = defineModel()
</script>
<template>
<input v-model="modelValue" />
</template>实际项目中的使用建议
什么时候用 defineModel
新的项目:直接使用 defineModel
老的项目:可以逐步迁移,优先修改经常使用的组件
团队项目:统一约定使用方式,保持代码一致性
需要注意的地方
版本要求:需要 Vue 3.4 或更高版本
类型支持:如果你用 TypeScript,defineModel 有很好的类型推断
浏览器兼容:和 Vue 3 的浏览器支持保持一致
性能考虑
defineModel 在性能上和原来的写法没有明显差别。它主要是在编译时进行转换,运行时的表现跟原来差不多。
总结
Vue 3.4 的 defineModel 确实让双向绑定的代码变得更加简洁。从原来需要写很多行代码,到现在只需要一行,这大大提高了开发效率。
这个改进不仅减少了代码量,也让代码更容易理解和维护。特别是对于新手来说,学习成本降低了,不用再记那些复杂的 props 和 emits 写法。
如果你还在用老版本的 Vue,可以考虑升级到 3.4 来体验这个新功能。如果你已经开始用 Vue 3.4,那么现在就可以在项目中使用 defineModel 来简化你的代码了。
好的工具应该让开发变得更简单,defineModel 正是这样一个好工具。它保留了 Vue 易用性的特点,同时让代码更加简洁明了。
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!