Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Python爬虫

  • 前端教程

    • 团队规范

    • Project

      • Axios用法与原理
      • css布局技巧
      • 深度学习平台术语
      • 谈谈前端天花板问题
      • 一个程序员的成长之路
      • Markdown-It 解析原理
      • minipack源码解析
      • PostCSS
      • Electron工程踩坑记录
      • H5 Video踩坑记录
      • Puppeteer翻页爬虫
      • 重构你的javascript代码
      • RxJS入门实践
      • 官网脚手架思考与实践
      • Stylelint样式规范工具
      • TypeScript开发Vue应用
      • Typescript tsconfig.json全解析
      • Vue项目TypeScript指南
      • TypeScript在Vue2.x中的坑
      • Vue Dialog弹窗解决方案
        • [#](#背景) 背景
        • [#](#好的方案) 好的方案
        • [#](#目标) 目标
        • [#](#实现机制) 实现机制
          • [#](#_1-dialog组件自动挂载到页面) 1. dialog组件自动挂载到页面
          • [#](#_2-api设计promise化) 2. API设计Promise化
      • Vue JSX插件依赖及语法实践
      • Webpack 模块打包原理
      • Webpack4 配置详解
      • Webpack4 devServer配置详解
      • Webpack3.x升级Webpack4指南
    • JS

    • NodeJS

    • Vue

    • React

    • 效率工具

    • 读书笔记

  • 教程
  • 前端教程
  • Project
wangmings
2022-07-19
目录

Vue Dialog弹窗解决方案

# # Vue Dialog弹窗解决方案

在做业务代码的modal弹窗时,总是围绕visible变量以及控制visible变量逻辑,能否简化弹窗相关逻辑呢?

如果想直接使用该解决方案,可以安装对应npm包,详细说明文档请在github中查看:@springleo/el-dialog-helper (opens new window) (opens new window)

该方案配合ElementUI或AntdV等组件库的modal组件更佳。

# # 背景

在业务中,如下代码一定不会陌生:

    <template>
        <div>
            your page logic
            <Dialog1
                :visible="dialogVisible1"
                :id="id" :name="name" ...
                @close="() => dialogVisible1 = false" />
            <Dialog1
                :visible="dialogVisible1"
                :id="id" :name="name" ...
                @close="() => dialogVisible1 = false" />
            <Dialog1
                :visible="dialogVisible1"
                :id="id" :name="name" ...
                @close="() => dialogVisible1 = false" />
            ...
        </div>
    </template>
    
    <script>
    import Dialog1 from './dialog1'
    import Dialog2 from './dialog2'
    import Dialog3 from './dialog3'
    
    export default {
        components: { Dialog1, Dialog2, Dialog3, ... }
        data() {
            return {
                dialogVisible1: false,
                dialogVisible2: false,
                dialogVisible3: false,
                ...
            }
        },
        methods: {
            openDialog1() { this.dialogVisible1 = true },
            openDialog2() { this.dialogVisible2 = true },
            openDialog3() { this.dialogVisible3 = true },
            ...
        }
    }
    </script>
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

它存在的问题在于:

  1. Dialog弹窗过多时,visible变量也相应增加
  2. 每个Dialog在组件中都需要注册并相应的初始化,繁琐并增加页面组件初始化时间
  3. visible变量控制繁琐

有没有更好的方式呢?

# # 好的方案

作为参照,我们可以很快联想到各个组件库的$confirm实现ConfirmDialog的实现。

它们主要实现方式一致,主要体现在API不同:

如使用Promise方式的ElementUI $confirm API:

    this.$confirm(props)
        .then(() => {})
        .catch(() => {})
    
1
2
3
4

另外一种是使用回调函数的方式,如Antdv $confirm API

    this.$confirm({
        ...props,
        onOk: () => {},
        onCancel: () => {},
    })
    
1
2
3
4
5
6

但ConfirmDialog是有固定的业务组件,而我们定义的Dialog是无法确定的,有没有办法让自定义Dialog拥有Promise API的调用方式?

# # 目标

从个人业务实践角度讲,较好的目标API是使用js API调用弹窗 + Promise API进行控制回调。如下:

    import Dialog1 from 'dialog1'
    
    this.$openDialog(Dialog1)(props)
        .then(data => {})
        .catch(err => {})
    
1
2
3
4
5
6

$confirm是因为有固定流程以及样式的ConfirmDialog组件,所以实现起来较为明确而简单,底层源码里也是直接引入ConfirmDialog (opens new window) (opens new window)组件,在这之上再进行包装。

但我们这里需要做通用的Dialog弹窗方式,该如何实现呢?

# # 实现机制

要实现上述API的Dialog弹窗解决方案,需要做到2步: 1. dialog组件自动挂载到页面 2. API设计Promise化

# # 1. dialog组件自动挂载到页面

通过vue源码我们知道,一个.vue文件,其实就是个Object对象(tempalte模板会被编译为对象的render函数)。同时也知道Vue Option API是通过new Vue({ Option API })方式转换为组件实例的。

此时我们这里如何把Object对象转换为Vue组件呢?Vue官方提供了Vue.extend (opens new window) (opens new window)这个API来返回Vue构造器。有了该构造函数,只需要实例化即可把Object Vue对象转为真正的具有上下文关系的Vue组件。同时执行$mount()方法即可挂载到指定节点上,并在UI上更新。

    const $openDialog = (component, propsData) => {
        const ComponentConstructor = Vue.extend(component);
        let instance = new ComponentConstructor({
          propsData,
        }).$mount(document.body);
        return instance
    }
    
1
2
3
4
5
6
7
8

# # 2. API设计Promise化

以上只是考虑了通过js api方式,手动添加Dialog,还需要考虑当用户关闭弹窗时,如何正常销毁Dialog。同时考虑到现实业务中,弹窗关闭通常都由弹窗内逻辑控制,所以需要设计相关API,把弹窗内逻辑和当前页逻辑进行解耦。

销毁Dialog的DOM,必然需要找到包裹的parent DOM,所以需要使用闭包来保存parent DOM。

Vue2.x组件实例本身也是一个发布订阅系统,其支持通过$emit和$once方式进行事件发布和订阅。所以当Dialog弹窗内完成业务时,只需要发布关闭事件即可,完全的业务方自主可控。同时为了业务方使用简化,API设计为Promise,使用.then/.catch来代替弹窗业务成功/失败。

    const $openDialog = (component) => {
      // 闭包存储
      const div = document.createElement('div');
      const el = document.createElement('div');
      div.appendChild(el);
      document.body.appendChild(div);
    
      const ComponentConstructor = Vue.extend(component);
      return (propsData = {}, parent = undefined) => {
        // 手动弹窗
        let instance = new ComponentConstructor({
          propsData,
          parent, // 父级上下文,设置了此参数可获得$store/$router等Provide对象
        }).$mount(el);
    
        // 关闭弹窗
        const destroyDialog = () => {
          if (instance && div.parentNode) {
            instance.$destroy();
            instance = null
            div.parentNode && div.parentNode.removeChild(div);
          }
        };
    
        // 使用.then/.catch来代替弹窗业务成功/失败
        return new Promise((resolve, reject) => {
          instance.$once("done", data => {
            destroyDialog();
            resolve(data);
          });
          instance.$once("cancel", data => {
            destroyDialog();
            reject(data);
          });
        });
      }
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

另外方案中还考虑了antdv/element-ui modal的便捷性,增加了visible控制,最终的解决方案源码可以看 lq782655835/el-dialog-helper (opens new window) (opens new window)

编辑 (opens new window)
TypeScript在Vue2.x中的坑
Vue JSX插件依赖及语法实践

← TypeScript在Vue2.x中的坑 Vue JSX插件依赖及语法实践→

最近更新
01
theme-vdoing-blog博客静态编译问题
09-16
02
搜索引擎
07-19
03
友情链接
07-19
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式