# 函数化弹窗组件

创新永远属于懒人!

参照:MessageBox - element-ui (opens new window)

第一次用到 element-ui 的弹窗时,惊为天人,因为之前用 Vuetify 的时候都是自己手撸完整的弹窗和逻辑,从没想过还能有 this.$confirm() 这么简便的调用方式。

但 Vue 的生命周期和脱离纯 .vue 用 js 写 Vue 一直是我的弱项,此前也尝试过将 Vuetify 的弹窗封装成这样,可惜最后以失败告终,不得不借用别人写的 vuetify-dialog (opens new window),而且还看不懂源码......

这一次,我直接从 element-ui 的源码入手,花了大概两个小时,总算是搞定了函数化弹窗组件的实现~

# 调用方式

我们的目标是模仿 element-ui,通过 MessageBox.confirm('弹窗文字') 函数式调用弹窗组件

import MessageBox from 'MessageBox.js'

Vue.extend({
  methods: {
    onClick() {
      MessageBox.confirm('是否确认').then(res => {}).catch(() => {})
    }
  }
})

# 实现方式

  1. 创建 MessageBox.vue 和 MessageBox.js

MessageBox.vue 用来编写弹窗组件的 HTML 结构和样式,还有必需的数据和逻辑。

MessageBox.js 用来进行导出的包装,同时也要负责 Vue 组件的创建、挂载和修改。

  1. 设计(搞清楚)从调用函数,到弹窗显示,到弹窗确认的流程

首先,MessageBox.confirm() 调用 MessageBox 函数。

MessageBox.confirm = (text) => {
  return MessageBox({
    text
  })
}

然后,MessageBox 函数返回一个 Promise 对象(所以我们可以在调用后接 then 和 catch 来处理结果)。

const MessageBox = function (options) {
  return new Promise((resolve, reject) => {
    show(options, resolve, reject)
  })
}

show 函数负责创建弹窗组件的 Vue 实例,将其挂载到 DOM 并根据传入的 options 修改其数据。

show 保存了这个 Promise 的 resolve 和 reject,以允许 Vue 组件通过回调向 Promise 传数据。

function show (options, resolve, reject) {
  if (!instance) {
    init()
  }
  // action 用于标明弹窗的动作类型,比如 confirm-提交/cancel-取消
  instance.action = ''
  // text 为自定义的弹窗提示文字
  instance.text = options.text
  // callback 提供给组件调用以实现向 Promise 传数据
  instance.callback = (action) => {
    if (action === 'confirm') {
      // inputVal 是从组件中传来的数据
      resolve(instance.inputVal)
    } else {
      reject()
    }
  }

  document.body.appendChild(instance.$el)
  Vue.nextTick(() => {
    // show 用于控制 MessageBox 组件的显隐
    instance.show = true
  })
}

这里的 instance 和 init() 就是一个简单的单例模式,保证全局只有一个 Vue 组件。

let instance

function init () {
  instance = new MessageBoxConstructor({
    el: document.createElement('div')
  })
}

以上都是 MessageBox.js 中的内容,下面让我们来看 MessageBox.vue 如何与 MessageBox.js 交互。

正如介绍 show 时所说,MessageBox.vue 通过调用 callback 与 MessageBox.js 通讯。

Vue.extend({
  methods: {
    doClose () {
      this.show = false
      setTimeout(() => {
        if (this.callback) {
          this.callback(this.action, this)
        }
      })
    }
  }
})

而 doClose 在动作按钮被点击改变 action 时被调用(这里的逻辑主要看具体场景和个人需求)。

Vue.extend({
  methods: {
    handleAction (action) {
      this.action = action
      this.doClose()
    }
  }
})

经过这一整个流程,我们就能实现:通过调用函数的方式显示弹窗 => 点击弹窗的取消/提交使弹窗关闭 => 弹窗中的数据通过 Promise 传来被我们的业务逻辑处理。


今天这个篇幅好像有些过长,完整的代码和使用请大家在 pass-no-fail (opens new window) 里找吧~

Last Updated: 8/30/2020, 10:41:35 PM