Vue3.4新功能:defineModel 让双向绑定更简单

摘要:Vue 3.4 带来了一个很实用的新功能:defineModel。这个功能让 v-model 双向绑定的代码变得特别简洁。以前需要写很多行的代码,现在一行就能搞定。

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,迁移过程很简单。可以一个一个组件慢慢改,不用一次性全部迁移。

迁移步骤:

  1. 删除 defineProps 中的 modelValue 定义

  2. 删除 defineEmits 中的 update:modelValue 定义

  3. 添加 const modelValue = defineModel()

  4. 把模板中的 :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

  • 老的项目:可以逐步迁移,优先修改经常使用的组件

  • 团队项目:统一约定使用方式,保持代码一致性

需要注意的地方

  1. 版本要求:需要 Vue 3.4 或更高版本

  2. 类型支持:如果你用 TypeScript,defineModel 有很好的类型推断

  3. 浏览器兼容:和 Vue 3 的浏览器支持保持一致

性能考虑

defineModel 在性能上和原来的写法没有明显差别。它主要是在编译时进行转换,运行时的表现跟原来差不多。


总结

Vue 3.4 的 defineModel 确实让双向绑定的代码变得更加简洁。从原来需要写很多行代码,到现在只需要一行,这大大提高了开发效率。

这个改进不仅减少了代码量,也让代码更容易理解和维护。特别是对于新手来说,学习成本降低了,不用再记那些复杂的 props 和 emits 写法。

如果你还在用老版本的 Vue,可以考虑升级到 3.4 来体验这个新功能。如果你已经开始用 Vue 3.4,那么现在就可以在项目中使用 defineModel 来简化你的代码了。

好的工具应该让开发变得更简单,defineModel 正是这样一个好工具。它保留了 Vue 易用性的特点,同时让代码更加简洁明了。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_12999