二次封装 el-dialog,async/await 异步弹框

1,528 阅读1分钟

先说说封装 dialog 的普通写法

参考el-dialog,使用 .sync 修饰符 + $emit('update:xx') 实现双向绑定 visible,并提供默认插槽

<dynamic-dialog title="demo" :visible.sync="visible">
      <my-form></my-form>
</dynamic-dialog>
​
// dynamic-dialog.vue
<template>
  <el-dialog
      :title="title"
      :visible.sync="visible">
      <slot></slot>
      <div slot="footer">
          <el-button @click="close">取 消</el-button>
          <el-button type="primary" @click="confirm">确 定</el-button>
      </div>
  </el-dialog>
</template>
<script>
export default {
    name: 'DynamicDialog',
    props: {
        title: String,
        visible: Boolean
    },
    methods: {
        confirm(){
            this.$emit('confirm')
            this.close()
        },
        close(){
            this.$emit('update:visible', false)
        },
    }
}
</script>

手动实现 v-model

通过 computed + props中的value, 实现双向绑定 visible

<dynamic-dialog v-model="visible" title="demo">
    <my-form></my-form>
</dynamic-dialog>

// dynamic-dialog.vue
... 省略重复代码
<script>
export default {
    props: {
        title: String,
        value: Boolean
    },
    computed: {
        visible: {
            get() {
                return this.value;
            },
            set(val) {
                this.$emit('input', val)
            }
        }
    },
    methods: {
        close() {
            this.visible = false
        }
    }
};
</script>

如果是业务组件 不用写插槽 直接封装成具体组件

// 选用户的弹框
<user-dialog
      v-model="visible"
      @close="cancel"
      @confirm="userSelected" />

它何必是个组件? Vue.extend实现动态挂载

大家有没有想过,要用到 dialog 的地方,大部分场景是一个动作。比如选择一个用户,理论上只要执行

const user = selectUser()

把打开弹框,选择用户,关闭弹框的操作封装在 selectUser方法里就行了。这样写可以使代码更加易读并且易维护。

要实现这个方法,首先了解一下 Vue.extend戳这里打开官网介绍

他返回一个类 Vue 的构造器,相当于它的子类,可以在合适的时机手动生成实例

dialog.js

import DynamicDialog from './DynamicDialog.vue'
import Vue from 'vue'
let instance = null;

const Dialog = Vue.extend(DynamicDialog);

Vue.prototype.$dialog = function(option) {
    if (!instance) {
        instance = new Dialog().$mount();
        document.body.appendChild(instance.$el);
    }
    return instance.open(option);
};

DynamicDialog.vue

<template>
  <el-dialog :title="title" :visible.sync="visible" v-bind="dialogOption">
    <component :is="component" v-bind="props" @cancel="cancel" @confirm="confirm"></component>
  </el-dialog>
</template>
<script>
export default {
  data() {
    return {
      props: {},
      title: '',
      dialogOption: {},
      component: null,
      visible: true,
      onClose: () => {},
      onConfirm: () => {},
    }
  },
  methods: {
    open({ title, dialogOption, component, props, onClose, onConfirm }){
      this.title = title
      this.dialogOption = dialogOption
      this.component = component
      this.props = props
      this.onClose = onClose
      this.onConfirm = onConfirm
    },
    confirm(...args) {
      this.close()
      this.onConfirm(...args)
    },
    cancel() {
      this.close()
      this.onClose()
    },
    close() {
      this.visible = false
    }
  },
};
</script>

如果需要选择名字中带“王”的用户,可以这么写

this.$dialog({
    title: '选择用户',
    dialogOption: { width: '600px' } // el-dialog 的属性
    component: UserSelecter, // 动态组件
    props: {
        nameLike: '王' // UserSelector 组件的属性
    },
    onClose(){
        console.log('取消选择')
    },
    onConfirm(user) {
        console.log('选择了:', user)
    }
})

避免回调地狱,使用 async/await

如何改进

以上写法虽然逻辑清晰,但是有几个问题:

  • 使用了回调函数 层级太多,不好看,不好维护
  • 如果在 this.$dialog() 方法下面还执行了某些操作,实际上是同步操作,对于新加入的小伙伴,很容易产生误导

使用 Promise可以解决回调问题,使用async/await是终极解决方案

// 改造 dialog

<script>
let resolve = null
export default {
  data() {
    return {
      visible: true,
      props: {},
      title: '',
      dialogOption: {},
      component: null,
    }
  },
  methods: {
    open({ title, dialogOption, component, props }){
      this.title = title
      this.dialogOption = dialogOption
      this.component = component
      this.props = props
      return new Promise((resolse, reject) => {
          resolve = resolse
      })
    },
    confirm(arg) {
      this.close()
      resolve(arg)
    },
    cancel() {
      this.close()
      resolve()
    },
    close() {
      this.visible = false
    }
  },
};
</script>

在业务中使用:

const user = await this.$dialog({
    title: '选择用户',
    dialogOption: { width: '600px' }
    component: UserSelecter,
    props: { nameLike: '王' }
})

if(!user) return

console.log('选择了用户:', user)

到这里我们已经实现了el-dialog + Vue.extend + async/await 优雅地实现弹框操作

针对业务再次封装

回到最初的起点,封装选择用户的方法

// utils.js
function selectUser(option) {
    return this.$dialog({
        title: '选择用户',
        dialogOption: { width: '600px' }
        component: UserSelecter, // 动态组件
        props: option
    })
}


// 业务代码
const user = await selectUser({ nameLike: '王' })
if(!user) return
console.log('选择了用户:', user)