一文搞懂 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转换器选项实现自定义修饰符。
如果我的文章对你有点帮助,欢迎关注公众号:【欧阳码农】,文章在公众号首发。你的支持就是我创作的最大动力,感谢感谢!
一文搞懂 Vue3 defineModel 双向绑定:告别繁琐代码!的更多相关文章
- 三文搞懂学会Docker容器技术(下)
接着上面一篇:三文搞懂学会Docker容器技术(上) 三文搞懂学会Docker容器技术(中) 7,Docker容器目录挂载 7.1 简介 容器目录挂载: 我们可以在创建容器的时候,将宿主机的目录与容器 ...
- 一文搞懂如何使用Node.js进行TCP网络通信
摘要: 网络是通信互联的基础,Node.js提供了net.http.dgram等模块,分别用来实现TCP.HTTP.UDP的通信,本文主要对使用Node.js的TCP通信部份进行实践记录. 本文分享自 ...
- 一文搞懂指标采集利器 Telegraf
作者| 姜闻名 来源|尔达 Erda 公众号 导读:为了让大家更好的了解 MSP 中 APM 系统的设计实现,我们决定编写一个<详聊微服务观测>系列文章,深入 APM 系统的产品.架构 ...
- 一文搞懂RAM、ROM、SDRAM、DRAM、DDR、flash等存储介质
一文搞懂RAM.ROM.SDRAM.DRAM.DDR.flash等存储介质 存储介质基本分类:ROM和RAM RAM:随机访问存储器(Random Access Memory),易失性.是与CPU直接 ...
- 基础篇|一文搞懂RNN(循环神经网络)
基础篇|一文搞懂RNN(循环神经网络) https://mp.weixin.qq.com/s/va1gmavl2ZESgnM7biORQg 神经网络基础 神经网络可以当做是能够拟合任意函数的黑盒子,只 ...
- 一文搞懂 Prometheus 的直方图
原文链接:一文搞懂 Prometheus 的直方图 Prometheus 中提供了四种指标类型(参考:Prometheus 的指标类型),其中直方图(Histogram)和摘要(Summary)是最复 ...
- Web端即时通讯基础知识补课:一文搞懂跨域的所有问题!
本文原作者: Wizey,作者博客:http://wenshixin.gitee.io,即时通讯网收录时有改动,感谢原作者的无私分享. 1.引言 典型的Web端即时通讯技术应用场景,主要有以下两种形式 ...
- 一文搞懂vim复制粘贴
转载自本人独立博客https://liushiming.cn/2020/01/18/copy-and-paste-in-vim/ 概述 复制粘贴是文本编辑最常用的功能,但是在vim中复制粘贴还是有点麻 ...
- 三文搞懂学会Docker容器技术(中)
接着上面一篇:三文搞懂学会Docker容器技术(上) 6,Docker容器 6.1 创建并启动容器 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] --na ...
- 一文搞懂所有Java集合面试题
Java集合 刚刚经历过秋招,看了大量的面经,顺便将常见的Java集合常考知识点总结了一下,并根据被问到的频率大致做了一个标注.一颗星表示知识点需要了解,被问到的频率不高,面试时起码能说个差不多.两颗 ...
随机推荐
- 师爷,翻译翻译什么叫AOP
张麻子:汤师爷,翻译翻译,什么叫AOP? 汤师爷:这还用翻译. 张麻子:我让你翻译给我听,什么叫AOP? 汤师爷:不用翻译,切面编程就是AOP啊. 黄四郎:难道你听不懂什么叫AOP? 张麻子:我就想让 ...
- CompletableFuture 使用详解
CompletableFuture 使用详解 1. runAsync 和 supplyAsync方法 CompletableFuture 提供了四个静态方法来创建一个异步操作. public stat ...
- 【转载】内存基本概念-watermark&lowmem_reserve
概述 当系统内存短缺的情况下仍去申请内存,可能会触发系统对内存的回收,那什么时候应该进行回收,回收到什么标准又可以停止回收,参考依据是什么?即本文将介绍的watermark(内存水位线),当检查wat ...
- vue如何实现v-model
- P1765
和那道题一样,这次用的getchar,结果对了可是洛谷评测WA了,换成scanf单个字符,结果还是WA了,换成直接getline读入整个字符串就对了. 可见读入单个字符的方式有可能出现各种小错,尤其是 ...
- C#排序算法6:快速排序
快速排序由C. A. R. Hoare在1960年提出.它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分 ...
- VS中多个源文件中只运行其中特定文件
1.问题 有时候一个项目中创建了多个源文件,但是我只想运行其中的一个,一起运行就会出现多个main入口的问题 2.解决方式 2.1 右键要排除的文件,点击属性 2.2 从生成中排除一项中选择是即可 2 ...
- P5704 【深基2.例6】字母转换
1.题目介绍 2.题解 2.1 ASCII码表 在ASCII码表中,小写字母=大写字母+32,则大写字母=小写字母-32 #include<iostream> using namespac ...
- 2023第十四届极客大挑战 — WEB WP
说明:由于是从docx直接导入,因此鉴于docx的识别,文章有些图片里面有红色下划线,但不影响! 属实懒了!直接导入了...哈哈.凑合看吧!实在太多了.... EzHttp Post传参 查看源码 访 ...
- 【css】 text-align 居中导航
原理 :利用 inline-block 将 导航 作为 文本 , 被外层具有 text-align 属性的导航盒子包含 .从而实现居中效果 1. html 结构 <header> < ...