前言: 明天在我的项目中遇到了很多很多须要弹出一个对话框的场景,因为之前全都是通过 v-if 来管制这个组件的显示与否,这样就造成了很多页面莫名多出了很多不相干的代码,极度不优雅。所以我尝试去实现了一个函数式调用的 dialog 组件,感觉在简略的场景下还是比拟好用的,特来分享一下这个思路。🎁
一. 后期筹备
你须要创立两个文件来和我一起实现这个函数式调用的 dialog,Dialog.vue
和 dialogCreator.ts
。
二. dialog 遮罩的款式
- 我的组件款式是采纳
UnoCss
的写法,是将款式内嵌在标签的class
属性里。和大家在Style
标签里写是截然不同的成果,大家不必特地放心款式写法的问题,款式和本文次要内容没有任何间接的关系。 - 这里咱们抉择先写一个遮罩,对于遮罩的关键点其实就是须要设置一个带一点点透明度的背景,我抉择了
rgba(0,0,0,0.4)
,也就是带0.4
透明度的纯黑背景色彩。在这里咱们须要特地留神,因为咱们的遮罩是会呈现在“其它页面之上”的,所以咱们须要给整个组件内部设置一个
absolute
来使它独立于其它页面,为了避免某些边界状况,须要设置z-index:9999
来保障这个页面会在整个利用之上。整体成果如下:
三. dialog 对话框的款式
- 对于 dialog 对话框的款式这里咱们不对立设置,然而咱们组件至多须要蕴含三个次要元素。一个 Header 区域, 一个 content 区域,最初一个 勾销按钮和确定按钮的区域。
- 在这里你能够先把文字都临时写成固定值,到前面我会解释如何通过 props 动静的传递这些值。
四. h 函数和 render 函数的用法
- 让咱们关上之前筹备的
dialogCreator.ts
文件,引入咱们刚刚编写的Dialog
组件,一会儿咱们就须要用到它了。 - 在此之前咱们还须要引入两位老朋友
h
,函数和render
函数。在这里看过我之前《如何创立一个全局搜寻框🔍》和《如何创立一个 Toast》这两篇文章的敌人肯定不会生疏这两个函数的意义,但为了关照新敌人我还是会大略解说一下这两个函数的主要用途的。 - 我置信大家对 Vue 渲染组件的流程有一个大略的认知,Vue 是先构建出 虚构 dom 而后再依据 虚构 dom 去渲染出 实在 dom的。
- 在这里咱们须要清晰的晓得,Vue 给咱们提供的的
template
标签仅仅只是一个让咱们能够用相熟的 html 标签书写 虚构 dom 的语法糖而已。是的,你没有听错,它仅仅只是一个语法糖而已,它底层是会被编译成用
h
函数发明出的 虚构 dom 在这里从而引出官网解释。 - 那么上文官网提到的渲染函数又是什么呢?其实就是刚刚咱们提到的
h
函数。h()
函数更精确名字其实应该是createVnode()
,和它的英文翻译是一一对应的,创立虚构 Dom。 - 这个函数具体该如何应用呢?咱们从实战去了解,让咱们持续编写咱们的
DialogCreator
类,咱们创立两个函数,一个管制 dialog 的呈现叫做present
办法,另一个管制 dialog 的隐没,叫做dismiss
办法。 - 这里马上就要用到刚刚提到的
h
函数。h
函数的第一个参数能够接管一个组件作为实参,并且返回这个组件的 虚构 dom 给咱们。所以咱们能够依照上面的写法拿到咱们所须要的 Dialog 组件的 虚构 dom。 - 拿到 虚构 dom 有什么用呢?这里须要引入咱们的第二个要害函数
render
函数。咱们须要晓得,咱们目前只拿到了一个游离于 实在 dom 节点 之外的一个“ 假的 dom”节点,你须要通知它该渲染到哪里。什么意思呢?关上咱们的main.ts
文件。千万不要遗记这个
#app
是什么。它就是咱们全局惟一的 实在 dom,一个朴实无华的一个 id 叫做 app 的 实在 dom。
- 而后咱们察看咱们
render
函数能够接管的参数类型是什么,看下图我画黄色线的中央,看到什么惊喜了吗?第一个参数是一个vnode
。什么?
vnode
,我刚刚不才通过h(Dialog)
函数拿到了一个vnode
吗?没错,聪慧的你应该能猜到上面的写法了。 - emm 然而如同在报错,咱们看一下错误信息。(这里咱们疏忽第三个参数,只思考两个参数即可。)
🤔,这个 container 参数的类型是一个
element
或者ShadomRoot
,这又是什么鬼呢?咱们持续点击render
函数,进入它的定义,发现 container 原来最终是一个HostElement
类型。看来这个搞清楚这个HostElement
是要害。
11. 在这里咱们转变一下思路,咱们反向推断 HostElement
是个什么。让咱们再次关上 main.ts
文件,这次咱们跳进 mount
函数的定义,就是上面黄色圈圈圈起来的这个函数。
你看到了什么?
没错很相熟的几个单词 HostElement
,留神,你千万不要感觉这个 HostElement
是什么很神奇的元素,让咱们回忆一下 mount
函数的参数是什么来着?
没错,还是那个普普通通的,一个叫做 app
的全局的 实在 dom。
- 由此咱们能够反向推断出,
render
函数须要一个 实在 dom 来包裹咱们的 虚构 dom。生产出一个 实在 dom 还不容易吗?咱们间接调取js
的办法,createElement(‘div’)
来生产一个一般的div
元素用来包裹咱们的 虚构 dom。
五. 欠缺 DialogCreator 类
- 当初也通知了 虚构 Dialog 组件该放在哪里了,接下来就须要将咱们的
containerEl
放在正确的地位,放在哪里呢?因为咱们的dialog
呈现的状况个别都是最顶层。揭示你一下,别忘了咱们所有其它页面都是被放到了id 为 app
的div
标签里。那么为了保障它相对呈现在最顶层而不被其它页面遮挡的这种状况产生,那咱们延长一下思路,如果让咱们的Dialog
成为body
标签的第一个子元素,并且因为之前咱们给Dialog
组件设置了absolute
属性,那么它就会正好浮现在咱们所有页面之上,因为它脱离了文档流,那么它的呈现就不会影响咱们其它页面的布局 - 思路有了,这还不简略吗?如何成为
body
的第一个子元素就是根底办法了,这里就不过多解释了。 - 而让元素隐没的办法就更简略了,适合的机会移除这个 dom 元素即可。
- 让咱们测试一下是否可行,咱们轻易在哪一个页面里去调用咱们的
DialogCreator
类调用 new 生成一个Dialog
实例。而后轻易写两个按钮去调用这两个办法测试一下。成果如下:
然而因为咱们的“遮罩”挡住了咱们的按钮,所以导致当初咱们点击不了隐没按钮,上面环节是本文的重点,如果传递咱们自定义的内容给
dialog
,来灵便生成一个高度自定义的dialog
。
六. 神奇的 h 函数
- 目前咱们的
dialog
曾经能够呈现到咱们的页面了,然而当初它的内容都是写死的,不灵便,咱们须要依照不同的场景传递不同的文字该如何实现呢?这里又须要请出咱们的老朋友,h
函数。 -
这里我先抛出概念,等等咱们一步一步验证。
h
函数是能够接管第二个参数的,并且第二个参数的值将被转换成 props 传递给咱们的组件。 - 让咱们回到
dialogCreator.ts
文件。咱们申明一个类型,筹备作为DialogCreator
外部constructor
参数的类型。并且申明两个类的属性
title
和content
来筹备做为props
传递给咱们的Dialog.vue
组件。 - 咱们当初还短少一个要害的货色,就是 勾销按钮 和确定按钮 的函数,咱们一并申明。(这里须要留神,个别勾销按钮就是敞开 dialog 对话框的性能,也就是类自身的 dismiss 办法,所以咱们不须要用户额定提供取按钮的函数,只须要提供确定时的回调函数即可。)
这里须要读者认真品尝上图代码的含意。
- 接下来咱们就须要传递
this.option
给h
函数即可。报错了没关系,是因为咱们还没有在
Dialog.vue
组件外部定义Props
。 - 让咱们别离从
dialogCreator.ts
文件导出这个DialogPropsType
类型,再从Dialog.vue
引入这个类型用来定义 props 即可。随即能够看到咱们刚刚到报错隐没了,阐明咱们的思路是没问题的。
七. 革新 Dialog.vue 组件
- 咱们先将之前固定写死的,
title
局部和content
局部替换成咱们申明的 props 里的title
和content
。 - 而后别忘了咱们
props
还寄存着《确定》和《勾销》的的办法。取出来别离搁置在这两个按钮身上。 - 轻易找一个其它页面,测试刚刚的
DialogCreator
类,内容我就轻易本人写了咱们测试一下:
八. 遮罩的敞开成果
- 当初咱们点击 遮罩 是没方法敞开 dialog 的,成果如下:
- 造成这种状况的起因也很简略,因为咱们的遮罩没有点击事件,怎么办呢?十分非常简单,给遮罩增加勾销
cancelBtn
,也就是 dismiss 办法不就能够了吗?测试一下,当初点击遮罩曾经能够失常敞开 dialog 了。
九. 修复冒泡造成的 Bug
- 目前看起来性能曾经很棒了,然而目前的代码会造成一个重大的
bug
,咱们在点击dialog
自身的时候,因为事件冒泡,会谬误的触发遮罩层的办法。 - 咱们验证一下,咱们轻易编写一个函数,而后绑定到
dialog
组件上。留神:这里的
dialog
指的是两头的那个实实在在的对话框自身,不是指整个组件。 - 而后给
cancelBtn
也加一行console.log
测试一下。成果如下:
- 解决办法简略的出乎你的预料,让咱们回到两头的
diaolog
身上,仅仅只须要绑定一个空的click
函数,而后加上修饰符stop
即可。 - 成果如下,能够看到,当初点击
dialog
曾经不会谬误的敞开整个 对话框了。至此咱们的
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 隐没的办法
}