前言: 今天在项目中遇到了很多很多需要弹出一个对话框的场景,由于之前全都是通过 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
是关键。
在这里我们转变一下思路,我们反向推断 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
实例。然后随便写两个按钮去调用这两个方法测试一下。
效果如下:
但是由于我们的“遮罩”挡住了我们的按钮,所以目前为止我们暂时点击不了消失按钮。别着急,我们一步一步尝试优化现在的代码。