一文搞懂 Vue3 defineModel 双向绑定:告别繁琐代码!
前言
随着vue3.4
版本的发布,defineModel
也正式转正了。它可以简化父子组件之间的双向绑定,是目前官方推荐的双向绑定实现方式。
vue3.4
以前如何实现双向绑定
大家应该都知道v-model
只是一个语法糖,实际就是给组件定义了modelValue
属性和监听update:modelValue
事件,所以我们以前要实现数据双向绑定需要给子组件定义一个modelValue
属性,并且在子组件内要更新modelValue
值时需要emit
出去一个update:modelValue
事件,将新的值作为第二个字段传出去。
我们来看一个简单的例子,父组件的代码如下:
<template> <CommonInput v-model="inputValue" /> </template> <script setup lang="ts"> import { ref } from "vue"; const inputValue = ref(); </script>
子组件的代码如下:
<template> <input :value="props.modelValue" @input="emit('update:modelValue', $event.target.value)" /> </template> <script setup lang="ts"> const props = defineProps(["modelValue"]); const emit = defineEmits(["update:modelValue"]); </script>
上面的例子大家应该很熟悉,以前都是这样去实现v-model
双向绑定的。但是存在一个问题就是input
输入框其实支持直接使用v-model
的,我们这里却没有使用v-model
而是在input
输入框上面添加value
属性和input
事件。
原因是因为从vue2
开始就已经是单向数据流,在子组件中是不能直接修改props
中的值。而是应该由子组件中抛出一个事件,由父组件去监听这个事件,然后去修改父组件中传递给props
的变量。如果这里我们给input
输入框直接加一个v-model="props.modelValue"
,那么其实是在子组件内直接修改props
中的modelValue
。由于单向数据流的原因,vue
是不支持直接修改props
的,所以我们才需要将代码写成上面的样子。
使用defineModel
实现数据双向绑定
defineModel
是一个宏,所以不需要从vue中import
导入,直接使用就可以了。这个宏可以用来声明一个双向绑定 prop,通过父组件的 v-model
来使用。
基础demo
父组件的代码和前面是一样的,如下:
<template> <CommonInput v-model="inputValue" /> </template> <script setup lang="ts"> import { ref } from "vue"; const inputValue = ref(); </script>
子组件的代码如下:
<template> <input v-model="model" /> </template> <script setup lang="ts"> const model = defineModel(); model.value = "xxx"; </script>
在上面的例子中我们直接将defineModel
的返回值使用v-model
绑定到input输入框上面,无需定义 modelValue
属性和监听 update:modelValue
事件,代码更加简洁。defineModel
的返回值是一个ref
,我们可以在子组件中修改model
变量的值,并且父组件中的inputValue
变量的值也会同步更新,这样就可以实现双向绑定。
那么问题来了,从vue2
开始就变成了单向数据流。这里修改子组件的值后,父组件的变量值也被修改了,那这不就变回了vue1
的双向数据流了吗?其实并不是这样的,这里还是单向数据流,我们接下来会简单讲一下defineModel
的实现原理。
实现原理
defineModel
其实就是在子组件内定义了一个叫model
的ref变量和modelValue
的props,并且watch
了props中的modelValue
。当props
中的modelValue
的值改变后会同步更新model
变量的值。并且当在子组件内改变model
变量的值后会抛出update:modelValue
事件,父组件收到这个事件后就会更新父组件中对应的变量值。
实现原理代码如下:
<template> <input v-model="model" /> </template> <script setup lang="ts"> import { ref, watch } from "vue"; const props = defineProps(["modelValue"]); const emit = defineEmits(["update:modelValue"]); const model = ref(); watch( () => props.modelValue, () => { model.value = props.modelValue; } ); watch(model, () => { emit("update:modelValue", model.value); }); </script>
看了上面的代码后你应该了解到了为什么可以在子组件内直接修改defineModel
的返回值后父组件对应的变量也会同步更新了吧。我们修改的其实是defineModel
返回的ref
变量,而不是直接修改props中的modelValue
。实现方式还是和vue3.4
以前实现双向绑定一样的,只是defineModel
这个宏帮我们将以前的那些繁琐的代码给封装到内部实现了。
其实defineModel
的源码中是使用 customRef 和 watchSyncEffect 去实现的,我这里是为了让大家能够更容易的明白defineModel
的实现原理才举的ref
和watch
的例子。如果大家对defineModel
的源码感兴趣,请在评论区留言,如果感兴趣的小伙伴比较多,我会在下一期出一篇defineModel
源码的文章。
defineModel
如何定义type
、default
等
既然defineModel
是声明了一个prop,那同样也可以定义prop的type
、default
。具体代码如下:
const model = defineModel({ type: String, default: "20" });
除了支持type
和default
,也支持required
和validator
,用法和定义prop
时一样。
defineModel
如何实现多个 v-model
绑定
同样也支持在父组件上面实现多个 v-model
绑定,这时我们给defineModel
传的第一个参数就不是对象了,而是一个字符串。
const model1 = defineModel("count1"); const model2 = defineModel("count2");
在父组件中使用v-model
时代码如下:
<CommonInput v-model:count1="inputValue1" /> <CommonInput v-model:count2="inputValue2" />
我们也可以在多个v-model
中定义type
、default
等
const model1 = defineModel("count1", { type: String, default: "aaa", });
defineModel
如何使用内置修饰符和自定义修饰符
如果要使用系统内置的修饰符比如trim
,父组件的写法还是和之前是一样的:
<CommonInput v-model.trim="inputValue" />
子组件也无需做任何修改,和上面其他的defineModel
例子是一样的:
const model = defineModel();
defineModel
也支持自定义修饰符,比如我们要实现一个将输入框的字母全部变成大写的uppercase
自定义修饰符,同时也需要使用内置的trim
修饰符。
我们的父组件代码如下:
<CommonInput v-model.trim.uppercase="inputValue" />
我们的子组件需要写成下面这样的:
<template> <input v-model="modelValue" /> </template> <script setup lang="ts"> const [modelValue, modelModifiers] = defineModel({ // get我们这里不需要 set(value) { if (modelModifiers.uppercase) { return value?.toUpperCase(); } }, }); </script>
这时我们给defineModel
传进去的第一个参数就是包含get
和 set
方法的对象,当对modelValue
变量进行读操作时会走到get
方法里面去,当对modelValue
变量进行写操作时会走到set
方法里面去。如果只需要对写操作进行拦截,那么可以不用写get
。
defineModel
的返回值也可以解构成两个变量,第一个变量就是我们前面几个例子的ref
对象,用于给v-model
绑定。第二个变量是一个对象,里面包含了有哪些修饰符,在这里我们有trim
和uppercase
两个修饰符,所以modelModifiers
的值为:
{ trim: true, uppercase: true }
在输入框进行输入时,就会走到set
方法里面,然后调用value?.toUpperCase()
就可以实现将输入的字母变成大写字母。
总结
这篇文章介绍了如何使用defineModel
宏实现双向绑定以及defineModel
的实现原理。
- 在子组件内调用
defineModel
宏会返回一个ref
对象,在子组件内可以直接对这个ref
对象进行赋值,父组件内的相应变量也会同步修改。 defineModel
其实就是在子组件内定义了一个ref变量和对应的prop,然后监听了对应的prop保持ref变量的值始终和对应的prop是一样的。在子组件内当修改ref变量值时会抛出一个事件给父组件,让父组件更新对应的变量值,从而实现双向绑定。- 使用
defineModel({ type: String, default: "20" })
就可以定义prop的type
和default
等选项。 - 使用
defineModel("count")
就可以实现多个v-model
绑定。 - 通过解构
defineModel()
的返回值拿到modelModifiers
修饰符对象,配合get
和set
转换器选项实现自定义修饰符。
关注公众号:【前端欧阳】,解锁我更多vue原理文章。还可以加我微信,让你的朋友圈多一位对vue有深入理解的人。也可以通过微信给我说你想看哪些vue原理文章,我会根据大家的反馈进行创作。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
微信客户端底层组件 MMKV 官宣支持 HarmonyOS NEXT
MMKV 是微信客户端自用的 key-value 组件,于 2018 年正式开源,支持 Android / macOS / Windows / POSIX 平台。 MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今在微信上使用,其性能和稳定性经过了时间的验证。 近期也已移植到 Android / macOS / Windows / POSIX / HarmonyOS NEXT 等平台,一并开源。 官方公告显示,最新发布的 MMKVv1.3.5 已正式支持HarmonyOS NEXT,目前大部分功能在该平台上可正常使用。 MMKV for HarmonyOS NEXT 使用文档:https://github.com/Tencent/MMKV/wiki/ohos_setup 此前一度有传言称,华为鸿蒙和微信之间的合作谈崩,但之后又有消息称华为与腾讯关于微信推送机制谈判结束,同意华为采用同 iOS 墓碑机制的推送方式,微信接入鸿蒙 NEXT 版本。 近日有接近华为的人士告诉《科创板日报》...
- 下一篇
开源日报 | “鸿蒙原生版”微信;小镇里的“大模型”;全网最刚游戏公司;半个AI圈向李彦宏「开炮」
欢迎阅读 OSCHINA 编辑部出品的开源日报,每天更新一期。 # 2024.4.25 今日要点 nginx 1.26.0 稳定版实验性支持 HTTP/3 主要变化 合并来自 1.25.x 主线分支的新功能和错误修复 实验性支持 HTTP/3 流模块 (stream module) 引入虚拟服务器 支持将流连接传递给监听套接字 HTTP/2 on aper-serverbasis 下载地址 苹果开源面向移动设备打造的小尺寸模型 OpenELM 苹果在 Hugging Face 平台上发布了一个「具有开源训练和推理框架的高效语言模型」,名为 OpenELM。OpenELM 使用分层缩放策略,可以有效地分配 Transformer 模型每一层的参数,从而提高准确率。例如,在参数量约为 10 亿的情况下,OpenELM 与 OLMo 相比准确率提升了 2.36%,同时所需的预训练 tokens 数量仅有原来的 50%。 OpenELM 有四种尺寸:2.7 亿、4.5 亿、11 亿和 30 亿个参数。而微软 Phi-3 模型为 38 亿,因此前者在小型机型上运行成本更低,可在手机和笔记本电脑等...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6