Vue3 如何实现一个带遮罩的 dialog 对话框

news2024/11/29 4:04:29

theme: mk-cute

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情

前言: 今天在项目中遇到了很多很多需要弹出一个对话框的场景,由于之前全都是通过 v-if 来控制这个组件的显示与否,这样就造成了很多页面莫名多出了很多不相关的代码,极度不优雅。所以我尝试去实现了一个函数式调用的 dialog 组件,感觉在简单的场景下还是比较好用的,特来分享一下这个思路。🎁

a.gif


一. 前期准备

你需要创建两个文件来和我一起完成这个函数式调用的 dialog
Dialog.vuedialogCreator.ts
image.png

二. dialog 遮罩的样式

  1. 我的组件样式是采用 UnoCss 的写法,是将样式内嵌在标签的class 属性里。和大家在 Style 标签里写是一模一样的效果,大家不用特别担心样式写法的问题,样式和本文主要内容没有任何直接的关系。

  2. 这里我们选择先写一个遮罩,关于遮罩的关键点其实就是需要设置一个带一点点透明度的背景,我选择了 rgba(0,0,0,0.4) ,也就是带 0.4 透明度的纯黑背景颜色。
    image.png
    在这里我们需要特别注意,由于我们的遮罩是会出现在“其它页面之上”的,所以我们需要给整个组件外部设置一个 absolute 来使它独立于其它页面,为了防止某些边界情况,需要设置 z-index:9999 来保证这个页面会在整个应用之上。整体效果如下:
    image.png

三. dialog 对话框的样式

  1. 关于 dialog 对话框的样式这里我们不统一设置,但是我们组件至少需要包含三个主要元素。一个 Header 区域,一个 content 区域,最后一个取消按钮和确定按钮的区域。
    image.png

  2. 在这里你可以先把文字都暂时写成固定值,到后面我会解释如何通过 props 动态的传递这些值。

四. h 函数和 render 函数的用法

  1. 让我们打开之前准备的 dialogCreator.ts 文件,引入我们刚刚编写的 Dialog 组件,一会儿我们就需要用到它了。
    image.png

  2. 在此之前我们还需要引入两位老朋友 h,函数和 render 函数。在这里看过我之前《如何创建一个全局搜索框🔍》 和《如何创建一个 Toast》 这两篇文章的朋友一定不会陌生这两个函数的意义,但为了照顾新朋友我还是会大概讲解一下这两个函数的主要用途的。
    image.png

  3. 我相信大家对 Vue 渲染组件的流程有一个大概的认知,Vue 是先构建出 虚拟dom 然后再根据 虚拟dom 去渲染出 真实dom的。

  4. 在这里我们需要清晰的知道, Vue 给我们提供的的 template 标签仅仅只是一个让我们可以用熟悉的 html 标签书写 虚拟dom 的语法糖而已。
    image.png
    是的,你没有听错,它仅仅只是一个语法糖而已,它底层是会被编译成用 h 函数创造出的 虚拟dom 在这里从而引出官方解释。
    image.png

  5. 那么上文官方提到的渲染函数又是什么呢?其实就是刚刚我们提到的 h 函数。h() 函数更准确名字其实应该是 createVnode(),和它的英文翻译是一一对应的,创建虚拟Dom。
    image.png

  6. 这个函数具体该如何使用呢?我们从实战去理解,让我们继续编写我们的 DialogCreator 类,我们创建两个函数,一个控制 dialog 的出现叫做 present 方法,另一个控制 dialog 的消失,叫做 dismiss 方法。
    image.png

  7. 这里马上就要用到刚刚提到的 h 函数。h 函数的第一个参数可以接收一个组件作为实参,并且返回这个组件的 虚拟dom 给我们。所以我们可以按照下面的写法拿到我们所需要的 Dialog 组件的 虚拟dom
    image.png

  8. 拿到 虚拟dom 有什么用呢?这里需要引入我们的第二个关键函数 render 函数。我们需要知道,我们目前只拿到了一个游离于 真实dom节点 之外的一个“假的dom”节点,你需要告诉它该渲染到哪里。什么意思呢?打开我们的 main.ts 文件。
    image.png
    千万不要忘记这个 #app 是什么。
    image.png
    它就是我们全局唯一的 真实dom ,一个朴实无华的一个 id 叫做 app真实dom。

  9. 然后我们观察我们 render 函数可以接收的参数类型是什么,看下图我画黄色线的地方,看到什么惊喜了吗?第一个参数是一个 vnode
    image.png
    什么?vnode,我刚刚不才通过 h(Dialog) 函数拿到了一个 vnode 吗?没错,聪明的你应该能猜到下面的写法了。
    image.png

  10. emm 但是好像在报错,我们看一下错误信息。(这里我们忽略第三个参数,只考虑两个参数即可。)
    image.png
    🤔,这个 container 参数的类型是一个 element 或者 ShadomRoot,这又是什么鬼呢?我们继续点击 render 函数,进入它的定义,发现 container 原来最终是一个 HostElement 类型。看来这个搞清楚这个 HostElement 是关键。
    image.png

  11. 在这里我们转变一下思路,我们反向推断 HostElement 是个什么。让我们再次打开 main.ts 文件,这次我们跳进 mount 函数的定义,就是下面黄色圈圈圈起来的这个函数。
    image.png
    你看到了什么?
    image.png
    没错很熟悉的几个单词 HostElement ,注意,你千万不要觉得这个 HostElement 是什么很神奇的元素,让我们回想一下 mount 函数的参数是什么来着?
    image.png
    没错,还是那个普普通通的,一个叫做 app 的全局的真实dom
    image.png

  12. 由此我们可以反向推断出,render 函数需要一个 真实dom 来包裹我们的虚拟dom。生产出一个 真实dom 还不容易吗?我们直接调取 js 的方法,createElement(‘div’) 来生产一个普通的 div 元素用来包裹我们的虚拟dom。
    image.png

五. 完善 DialogCreator 类

  1. 现在也告诉了虚拟Dialog 组件该放在哪里了,接下来就需要将我们的 containerEl 放在正确的位置,放在哪里呢?由于我们的 dialog 出现的情况一般都是最顶层。提醒你一下,别忘了我们所有其它页面都是被放到了 id为 appdiv 标签里。那么为了保证它绝对出现在最顶层而不被其它页面遮挡的这种情况发生,那我们延伸一下思路,如果让我们的 Dialog 成为 body 标签的第一个子元素,并且由于之前我们给 Dialog 组件设置了 absolute 属性,那么它就会正好浮现在我们所有页面之上,由于它脱离了文档流,那么它的出现就不会影响我们其它页面的布局
    image.png

  2. 思路有了,这还不简单吗?如何成为 body 的第一个子元素就是基础方法了,这里就不过多解释了。
    image.png

  3. 而让元素消失的方法就更简单了,合适的时机移除这个 dom 元素即可。
    image.png

  4. 让我们测试一下是否可行,我们随便在哪一个页面里去调用我们的 DialogCreator 类调用 new 生成一个 Dialog 实例。然后随便写两个按钮去调用这两个方法测试一下。
    image.png
    效果如下:
    啊.gif
    但是由于我们的“遮罩”挡住了我们的按钮,所以目前为止我们暂时点击不了消失按钮。别着急,我们一步一步尝试优化现在的代码。

六. 神奇的 h 函数

  1. 目前我们的 dialog 已经可以出现到我们的页面了,但是现在它的内容都是写死的,不灵活,我们需要按照不同的场景传递不同的文字该如何实现呢?这里又需要请出我们的老朋友,h 函数。

  2. 这里我先抛出概念,等等我们一步一步验证。

h 函数是可以接收第二个参数的,并且第二个参数的值将被转换成 props 传递给我们的组件。

  1. 让我们回到 dialogCreator.ts 文件。我们声明一个类型,准备作为 DialogCreator 内部 constructor 参数的类型。
    image.png
    并且声明两个类的属性 titlecontent 来准备做为 props 传递给我们的 Dialog.vue 组件。
    image.png

  2. 我们现在还缺少一个关键的东西,就是取消按钮确定按钮的函数,我们一并声明。(这里需要注意,一般取消按钮就是关闭 dialog 对话框的功能,也就是类本身的 dismiss 方法,所以我们不需要用户额外提供取按钮的函数,只需要提供确定时的回调函数即可。)
    image.png
    这里需要读者仔细品味上图代码的含义。

  3. 接下来我们就需要传递 this.optionh 函数即可。

    image.png

    报错了没关系,是因为我们还没有在 Dialog.vue 组件内部定义 Props

  4. 让我们分别从 dialogCreator.ts 文件导出这个 DialogPropsType 类型,再从 Dialog.vue 引入这个类型用来定义 props 即可。

    image.png

    随即可以看到我们刚刚到报错消失了,说明我们的思路是没问题的。

    image.png

七. 改造 Dialog.vue 组件

  1. 我们先将之前固定写死的,title 部分和 content 部分替换成我们声明的 props 里的 titlecontent
    image.png

  2. 然后别忘了我们 props 还存放着《确定》和《取消》的的方法。取出来分别放置在这两个按钮身上。
    image.png

  3. 随便找一个其它页面,测试刚刚的 DialogCreator 类,内容我就随便自己写了
    image.png
    我们测试一下:
    啊.gif

八. 遮罩的关闭效果

  1. 现在我们点击遮罩是没办法关闭 dialog 的,效果如下:
    a.gif

  2. 造成这种情况的原因也很简单,因为我们的遮罩没有点击事件,怎么办呢?非常非常简单,给遮罩添加取消 cancelBtn ,也就是 dismiss 方法不就可以了吗?
    image.png
    测试一下,现在点击遮罩已经可以正常关闭 dialog 了。
    啊.gif

九. 修复冒泡造成的 Bug

  1. 目前看起来功能已经很棒了,但是目前的代码会造成一个严重的 bug,我们在点击 dialog 本身的时候,由于事件冒泡,会错误的触发遮罩层的方法。
    a.gif

  2. 我们验证一下,我们随便编写一个函数,然后绑定到 dialog 组件上。
    image.png
    注意:这里的 dialog 指的是中间的那个实实在在的对话框本身,不是指整个组件。
    image.png
    image.png

  3. 然后给 cancelBtn 也加一行 console.log 测试一下。
    image.png
    效果如下:
    a.gif

  4. 解决方法简单的出乎你的意料,让我们回到中间的 diaolog 身上,仅仅只需要绑定一个空的 click 函数,然后加上修饰符 stop 即可。
    image.png

  5. 效果如下,可以看到,现在点击 dialog 已经不会错误的关闭整个 对话框了。
    QQ20221229-225519-HD.gif
    至此我们的 dialog 组件已经可以在绝大部分场景下使用了。🎁~

总结

目前的代码只是一个很粗糙的实现,更加具体实用的功能还需读者根据自己项目的需求自行完成。下面是 DialogCreator.ts 文件的代码。读者可根据需要自行查阅。

import Dialog from "./Dialog.vue";

import { h, render } from "vue";

interface DialogType {
  title: string;
  content: string;
  confirmBtn: () => void;
}

export interface DialogPropsType extends DialogType {
  closeBtn: () => void;
}
export class DialogCreator {
  containerEl: HTMLDivElement;
  option: DialogPropsType;
  constructor(option: DialogType) {
    this.containerEl = document.createElement("div");
    this.option = { ...option, closeBtn: this.disMiss.bind(this) };
  }

  present() {
    const vnode = h(Dialog, this.option);
    render(vnode, this.containerEl);
    document.body.insertBefore(this.containerEl, document.body.firstChild);
  }

  disMiss() {
    render(null, this.containerEl);
    document.body.removeChild(this.containerEl);
  } //dialog 消失的方法
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/344883.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【python游戏】让我们一起制作地球联邦阵营的战机,保护希望水晶,为人类的希望而战。

前言 嗨喽~大家好呀,这里是魔王呐 ❤ ~! 随着人类太空科技的飞速发展,希望水晶被越来越多的科学家当做核心能源来开发使用。 人类社会也因为水晶资源的争夺,开始逐渐分化成两派。 留在地球的普通人成立地球联邦,移居卫星的新人…

我对平衡二叉树的理解(比喻的方式)

传销是一种恶性的行销方式,主要手段就是激励其中的成员拉人头。 有个奇怪的传销组织,他们的传销规则是这样的: 每个人最多可以带着2人进该组织,其中1个年纪比自己大,另1个年纪比自己小新人都是由创始人找到。假如年纪…

中文关键词提取算法

中文关键词提取算法 如何提取query或者文档的关键词? 一般有两种解决思路: 有监督方法,把关键词提取问题当做分类问题,文本分词后标记各词的重要性打分,然后挑出重要的topK个词;无监督方法,使…

likeshop单商户SaaS版V1.8.2说明!

likeshop单商户SaaS版V1.8.2主要更新如下: 新增 前端登录引导用户填写头像昵称 PC端—注册页面显示服务协议和隐私政策 PC端—首次进入商城弹出协议提示 PC端—结算页新增门店自提的配送方式 后台—PC端菜单导航栏的跳转链接支持添加自定义链接 ​​ ​​ ​ 优…

2022年“网络安全”赛项宜昌市选拔赛 任务书

2022年“网络安全”赛项宜昌市选拔赛 任务书 任务书 一、竞赛时间 共计3小时。 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段单兵模式系统渗透测试 任务一 数据库服务渗透测试 任务二 Wireshark数据包分析 任务三 Windows操作系统渗透测试 任务四 系统漏…

腾讯云企业网盘正式入驻数字工具箱

腾讯技术公益继腾讯电子签等入驻后,上线近半年的腾讯技术公益数字工具箱再次迎来新成员——腾讯云企业网盘,现已正式接受公益机构申请公益权益。腾讯云企业网盘(https://pan.tencent.com)是由腾讯云推出的一款安全、高效、开放的企…

python+flask开发mock服务

目录 什么是mock? 什么时候需要用到mock? 如何实现? pythonflask自定义mock服务的步骤 一、环境搭建 1、安装flask插件 2、验证插件 二、mock案例 1、模拟 返回结果 2、模拟 异常响应状态码 3、模拟登录,从jmeter中获取…

Kafka 消费者

与生产者对应的是消费者,应用程序可以通过 KafkaConsumer 来订阅主题,并从订阅主题中拉取消息。 消息者与消费组 消费者(Consumer)负责订阅 Kafka 中的主题(Topic),并且从订阅的主题上拉取消息…

低代码开发平台|制造管理-生产过程管理搭建指南

1、简介1.1、案例简介本文将介绍,如何搭建制造管理-生产过程。1.2、应用场景先填充工序信息,再设置工艺路线对应的工序;工序信息及工艺路线列表报表展示的是所有工序、工艺路线信息,可进行新增对应数据的操作。2、设置方法2.1、表…

女生做大数据有发展前景吗?

当前大数据发展前景非常不错,且大数据领域对于人才类型的需求比较多元化,女生学习大数据也会有比较多的工作机会。大数据是一个交叉学科涉及到的知识量比较大学习有一定的难度,女生比较适合大数据采集和大数据分析方向的工作岗位。 大数据采…

【沁恒WCH CH32V307V-R1与Arduino的串口通讯】

【沁恒WCH CH32V307V-R1的单线半双工模式串口通讯】1. 前言2. 软件配置2.1 安装MounRiver Studio3. UASRT项目测试3.1 打开UASRT工程3.2 CH307串口发送数据到Arduino实验3.3 CH307串口接收数据Arduino实验5. 小结1. 前言 本例演示了采用CH307串口3与Arduino软串口收发通信&…

Python的深、浅拷贝到底是怎么回事?一篇解决问题

嗨害大家好鸭!我是小熊猫~ 一、赋值 Python中, 对象的赋值都是进行对象引用(内存地址)传递, 赋值(), 就是创建了对象的一个新的引用, 修改其中任意一个变量都会影响到另一个 will …

第七届蓝桥杯省赛——5分小组

题目:9名运动员参加比赛,需要分3组进行预赛。有哪些分组的方案呢?我们标记运动员为 A,B,C,... I下面的程序列出了所有的分组方法。该程序的正常输出为:ABC DEF GHIABC DEG FHIABC DEH FGIABC DEI FGHABC DFG EHIABC DFH EGIABC DF…

VFIO软件依赖——VFIO协议

文章目录背景PCI设备模拟PCI设备抽象VFIO协议实验Q&A背景 在虚拟化应用场景中,虚拟机想要在访问PCI设备时达到IO性能最优,最直接的方法就是将物理设备暴露给虚拟机,虚拟机对设备的访问不经过任何中间层的转换,没有虚拟化的损…

2023年小鹏新能源汽车核心部件解密

小鹏主要硬件清单(G9车型) 感知层 从硬件上看,G9搭载两颗NVIDIA DRIVE Orin 智能辅助驾驶芯片,算力达到 508 TOPS。此外,全车周边31 个感知元器件,(800万双目、4个300万侧前侧后、4个130万环视、1个170万后视、1个100万DMS)、12个超声波雷达、5个毫米波雷达、2个速…

TeamFiltration:一款针对O365 AAD账号安全的测试框架

关于TeamFiltration TeamFiltration是一款针对O365 AAD账号安全的跨平台安全测试框架,在该工具的帮助下,广大研究人员可以轻松对O365 AAD账号进行枚举、喷射、过滤和后门植入等操作。TeamFiltering与CrackMapExec非常相似,它可以创建并维护一…

四大垃圾回收算法七大垃圾回收器

JVM的运行时内存也叫做JVM堆,从GC的角度可以将JVM分为新生代、老年代和永久代。其中新生代默认占1/3堆内存空间,老年代默认占2/3堆内存空间,永久代占非常少的对内存空间。新生代又分为Eden区、SurvivorFrom区和SurvivorTo区, Eden…

Python基础知识复习以及第三方库openxel的初步使用

目录文件python文件打开函数Python中的split函数详细解释List对象list添加元素的三种方法删除元素反转Python第三方库openxel的初步使用excel文件对象open操作excel入门文件 python文件打开函数 语法:open(file, mode ‘r’, buffering-1, encodingNon…

【高性价比】初学者入门吉他值得推荐购买的民谣单板吉他品牌—VEAZEN费森吉他

“在未知的世界里,我们是一群不疲不倦的行者,执念于真善美,热衷于事物的极致。我们抽丝剥茧,不断地打败自己,超越自己,我们无所畏惧终将成为巨人。”这是VEAZEN吉他官网首页上很明显的一段话,也…

Learning C++ No.9【STL No.1】

引言: 北京时间:2023/2/13/18:29,开学正式上课第一天,直接上午一节思想政治,下午一节思想政治,生怕我们……,但,我深知该课的无聊,所以充分利用时间,把我的小…