更强大、更灵活! defineModel 重新定义双向绑定

news/2025/2/22 0:53:56/

前言

在 Vue 3.4 中, defineModel 宏的引入标志着 Vue 双向绑定机制的一次重大革新。作为 Composition API的重要补充, defineModel 不仅简化了代码结构,还显著提升了开发效率和代码可维护性。本文将深入探讨 defineModel 的核心原理、最佳实践以及在实际项目中的应用场景,展示其如何优雅地解决传统 v-model 实现中的痛点。

传统双向绑定的痛点

defineModel 出现之前,Vue 的双向绑定主要依赖于 v-model 和手动管理 props emits 。虽然这些方法有效,但在复杂场景下,代码往往显得冗长且难以维护。

方案一:手动管理 props emits

1. 父组件传递数据的同时需要实现一个修改数据的方法传递给子组件

<!-- 父组件 -->  
<child :carObj="carObj" @carPriceAdd="carPriceAdd" />  <script setup lang="ts">  
const carObj = ref<ICarObj>({  brand: 'BMW',  price: 100000  
})  const carPriceAdd = () => {  carObj.value.price += 1000  
}  
</script>  

2. 子组件接收数据的同时还需要接收父组件传递过来的事件,并通过 emits 调用,就可以修改父组件的数据了

<script setup lang="ts">  
const props = defineProps<{  modelValue: IUser,  // v-model  carObj: ICarObj // v-bind  
}>()  
const emits = defineEmits(['carPriceAdd'])  
const priceAdd = () => {  emits('carPriceAdd')  console.log(props.carObj.price)  
}  
</script>  

方案二:使用 v-model

还可以借助 v-model ,可以省去父组件定义修改数据的方法并传递给子组件这一步

1. 父组件通过 v-model 传递数据给子组件

<child v-model="user" />  
<script setup lang="ts">  
const user = ref<IUser>({  name: 'song',  age: 18  
})  
</script>  

2. 子组件在接受数据的同时也还要接受事件,只不过这个事件并不是父组件显式传递过来的,并且格式有点区别

<script setup lang="ts">  
const props = defineProps<{  modelValue: IUser,  // v-model  carObj: ICarObj // v-bind  
}>()  
const emits = defineEmits(['update:modelValue'])  
const ageAdd = () => {  emits('update:modelValue', {  ...props.modelValue,  age: props.modelValue.age + 1  })  // console.log(props.modelValue.age)  
}  
</script>  
  • v-model 默认传递过来的参数名为: ** modelValue ** ,默认传递过来的事件为: ** update:modelValue **

  • 默认参数名在父组件中可以修改,格式为: v-model:name ,同理子组件中接受的数据名与事件名改成一致即可

尽管 v-model 简化了部分代码,但仍需手动管理 props emits ,尤其是在处理多个双向绑定时,代码复杂度显著增加。所以从 Vue 3.4 开始,官方更加推荐使用 defineModel() 宏来实现双向数据绑定。

defineModel 的诞生:简化双向绑定

defineModel 是一个编译器宏,用于在 Vue 组件中定义双向绑定的 prop。它本质上是对 v-model指令的语法糖,但提供了更简洁、更直观的语法。

基本用法

父组件还是不变,只需通过 v-model 传递数据给子组件即可

<child v-model="user" />  
<script setup lang="ts">  
const user = ref<IUser>({  name: 'song',  age: 18  
})  
</script>  

通过 defineModel ,子组件无需再显式接收 props emits ,直接通过 defineModel 返回的 ref 对象即可实现双向绑定。

<script setup lang="ts">  
// 通过defineModel声明父组件传递过来的数据,返回一个ref对象  
const user = defineModel<IUser>('user', {  default: {}  
})  
// 子组件可以直接修改刚刚通过defineModel声明的数据,不需要通过emits,父组件会自动更新  
const ageAdd = () => {  user.value.age += 1  
}  
</script>  

修饰符与转换器

在一些特殊场景下,我们可能还需要使用 v-model 的修饰符功能

比如:清除字符串末尾的空格

** 父组件添加修饰符 **

<!-- 父组件 -->  
<child v-model:userName.trim="userName" />  

** 子组件获取修饰符 **

在子组件中,我们可以通过解构 defineModel() 的返回值,来获取父组件添加到子组件 v-model 的修饰符:

// 通过defineModel声明父组件传递过来的数据,返回一个ref对象  
const [user, filters] = defineModel<IUser>({  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  }  
})  

多Model

我们可以在单个组件实例上创建多个 v-model 的双向绑定

比如:

<!-- 父组件 -->  
<child v-model.trim="user" v-model:userName.trim.number="userName" />  

子组件同时接受多个 ` v-model

// 通过defineModel声明父组件传递过来的数据,返回一个ref对象  
const [user, filters] = defineModel<IUser>({  default: {},  set: (val) => {  console.log('set', val)  }  
})  const [userName, userNameFilters] = defineModel<string>('userName',{  default: '',  set: (val) => {  if(userNameFilters.trim) {  return val.trim()  }  return val  }  
})  

实现原理: defineModel 的背后

了解了怎么用的,最后再来看看它是怎么实现的

我们知道 defineModel 其实就是 v-model 的语法糖,所以我们可以对比下两种写法最后的编译结果有什么区别?

使用 defineModel 后,我们在组件中虽然可以不用像之前那样显式的接收props与emits,但Vue同样会帮我们生成这两块内容,并且可以看到两者红框内基本一样,只不过使用 defineModel 会多一个修饰符的接收defineModel 会被编译成一个 _useModel 方法,这是实现双向绑定的核心。从编译后的代码可以看出, defineModel 会接收父组件传递的 props emits ,并利用 props 中的值进行初始化。当数据需要更新时,它会调用 emits 中注册的事件来通知父组件。然而,在实际开发中,我们通常不会直接操作 propsemits ,而是通过 defineModel 返回的 ref 值来直接操作数据。因此, _useModel 的核心任务是确保这个 ref 值与父组件传递的 props 值保持同步,从而实现数据的双向绑定。

结语: defineModel 的未来

defineModel 的引入不仅简化了 Vue 中的双向绑定,还为开发者提供了更强大的工具来处理复杂的数据流。随着 Vue 生态的不断发展,defineModel 必将在更多场景中发挥其重要作用,成为 Vue 开发者的得力助手。


http://www.ppmy.cn/news/1573748.html

相关文章

notepad++右键菜单不见了

卸载时没点击完成&#xff0c;又重新安装了一个&#xff0c;最终导致了一些bug&#xff0c;导致右键没有notepad菜单。 解决方式&#xff1a; 新建一个register.reg文件&#xff0c;加入以下代码&#xff0c;然后双击执行即可 代码说明&#xff1a;Open with Notepad 是右…

从零到一实现微信小程序计划时钟:完整教程

在本教程中&#xff0c;我们将一起实现一个微信小程序——计划时钟。这个小程序的核心功能是帮助用户添加任务、设置任务的时间范围&#xff0c;并且能够删除和查看已添加的任务。通过以下步骤&#xff0c;我们将带你从零开始实现一个具有基本功能的微信小程序计划时钟。 项目…

Vue 3 工程化打包工具:从理论到实践 (下篇)

引言 在前端开发中&#xff0c;打包工具是工程化的重要组成部分。Vue 3 作为当前流行的前端框架&#xff0c;其工程化离不开高效的打包工具。打包工具不仅能够将代码、样式、图片等资源进行优化和压缩&#xff0c;还能通过模块化、代码分割等功能提升应用的性能。本文将深入探…

DeepSeek预测25考研分数线

25考研分数马上要出了。 目前&#xff0c;多所大学已经陆续给出了分数查分时间&#xff0c;综合往年情况来看&#xff0c;每年的查分时间一般集中在2月底。 等待出成绩的日子&#xff0c;学子们的心情是万分焦急&#xff0c;小编用最近爆火的“活人感”十足的DeepSeek帮大家预…

DeepSeek术语笔记

1&#xff09;性能接近闭源模型&#xff08;如GPT-4&#xff09;&#xff0c;但训练和部署成本更低 2&#xff09;DeepSeek R1&#xff0c;专注于数理逻辑与深度推理&#xff0c;例如解决高等数学、信号与系统等复杂问题&#xff0c;并通过知识蒸馏提升推理能力 DeepSeek V3&…

帝国CMS如何做安全加固,防webshell、防篡改、防劫持

帝国CMS是一款非常知名的CMS系统&#xff0c;采用PHPMySQL开发&#xff0c;开源免费、功能强大、结构清晰&#xff0c;深受广大用户喜欢。 虽然帝国CMS非常优秀&#xff0c;但是作为站长来说&#xff0c;为了保障网站安全&#xff0c;还是需要做一些必要的安全防护措施。今天我…

Webpack,Vite打包的理解

Webpack 和 Vite 都是现代前端开发中常用的构建工具&#xff0c;用于打包和优化项目代码。尽管它们的目标相似&#xff0c;但在设计理念、工作方式和适用场景上存在显著差异。 Webpack Webpack 是一个模块打包工具&#xff0c;主要用于将多个模块&#xff08;如 JavaScript、…

Edge浏览器翻译|自动翻译设置

文章目录 Edge浏览器翻译|自动翻译设置右键翻译显示原文 Edge浏览器翻译|自动翻译设置 在 Microsoft Edge 浏览器中使用 Microsoft Translator - Microsoft 支持 进入浏览器设置,从首选语言列表中移除多余的语言设置 网站将以受支持语言列表中的第一种语言进行显示。若要重新…