概述
众所周知,Vuetify 提供了 Dialog、Snackbar 等的提示组件,但是他并没有像 Element 那样提供全局或局部的相关调用函数,比如 showDialog
,所以要实现这个需求,得自己去封装一个函数。
功能设计
作为一个通用函数,在功能上肯定是越泛用越好,但又不能把所有的参数都交由开发者去调,这样就失去封装的意义。
描述一个 Dialog
首先考虑 Dialog 是什么和为什么需要它,Dialog 可以是提示框,让用户知道某个操作有风险,也可以是选择框,让用户选择是否接受条款,还可以是输入框,让用户提交自己的信息。
从功能上可以分成这三类,但这还是很抽象,输入框提交哪些数据?选择框的选项有哪些?提示框的内容样式有没有要求?如果要用一个对象来描述 Dialog,很难做到这么详细,如果将每一项需求都作为一个属性存入对象,那么这个对象将会很复杂,光是属性名就得记半天。
所以应该从 “Dialog 是怎么样的” 这个问题入手去考虑,常见的 Dialog,它在布局上可以分成 3 块,从上往下分别是标题栏、内容栏和动作栏,标题栏就是展示标题字符串的,内容栏不确定,动作栏是一组按钮,数量不确定。那么根据这些信息可以构建出如下的配置:
1 | { |
按钮数量不定,但是样式是类似的,只是文字、颜色、动作不一样,所以按钮可以使用如下对象描述:
1 | { |
之所以不把按钮的动作放入对象中,是因为动作中会涉及到上下文,而在对象中不好指定上下文,所以只提供一个 closable
来控制按下后是否关闭 Dialog,为了提高灵活性,我会在按钮按下后通过 $emit
将自定义事件传递给调用 showDialog
的组件,所以还需要一个属性去记录调用 Dialog 的组件的上下文。
现在的问题就在内容栏了, 要如何去描述”提示框”、”输入框”和”选择框”呢?从最简单的开始,提示框只是显示文字的,可以直接用一个字符串属性去描述,剩下的两种类型的具体表现是不确定的,所以可以将它们看成是组件,动态地将组件显示出来,通过 component
标签实现。
实际上动态渲染组件这种方式是万能的,甚至”提示框”都能通过这种方式实现,但是提示框的功能太简单了,没必要单独弄个组件去显示(这样反而更加复杂),所以可以将配置项独立出来,如果要显示复杂样式的提示框,也能使用动态渲染去实现。
现在确定的配置如下:
1 | { |
储存 Dialog 的状态
由于 Dialog 是全局的,所以这个状态应该存放在 Vuex 中,showDialog
和 closeDialog
作为两个 Mutation,通过 showDialog
将配置放入 state.dialog
对象,调用 closeDialog
之后将 state.dialog
内容清空。
1 | showDialog (state, payload) { |
为什么关闭 Dialog 后不把 state.dialog
设置为 null 呢?原因在于使用 null 之后,Dialog 组件中的相关取值处需要做多一次 null 判断,比如说 dialog && dialog.title
否则就容易出错,而当 dialog 为空对象时,取一个不存在的属性,返回的是 undefined,并不会报错。
确定 Dialog 的结构
根据刚刚提到的 Dialog 的三个部分 ——— 标题栏、内容栏和动作栏,可以确定一个大致的结构:
1 | <v-dialog |
标题栏是确定的,所以写好了,内容栏得分成两种情况,动作栏需要对 btns 做一个遍历。所以内容栏和动作栏分别可以写成:
1 | <!-- 内容栏 --> |
处理 Dialog 的动作
从上面的代码片段可以看出,对按钮动作的处理全靠 handleClick 函数完成,这个函数的具体功能前文已经说过了,在按钮被点击后向调用 Dialog 的组件发一个自定义事件,让那个组件去做处理,接着根据 closable 属性去决定是否关闭 Dialog。
1 | handleClick (btn) { |
传递 Component 的状态
其实这一节应该和上一节合并的,这一节要讲的是获取动态渲染组件的状态,毕竟 Dialog 作为输入框时,就是为了获得输入框中的文字。
showDialog
设置 Vuex 的状态,closeDialog
清空状态,并没有很好的方式去获得自定义组件的内容,所以这里我使用了 ref 去获取组件的实例,直接将实例通过 handleClick 传递给使用 Dialog 的组件,我的想法是”既然调用了 Dialog 去显示自定义组件,那肯定知道自定义组件内哪些属性是需要的,可以自行取用“。
这里可以通过在自定义组件内设置一个函数去搜集状态,比如:
1 | returnValue () { |
要考虑两种情况,一是如果开发者没注意,漏写了这个函数,那么就无法正确搜集状态,二是函数名,可能与组件内部相关属性冲突,所以我还是选择了直接传递组件实例。
完整的 Dialog 组件
1 | <template> |