vue3实现函数式弹窗并返回promise

830 阅读2分钟

场景

  1. 有一个表格
  2. 有一个列用的自定义组件
  3. 这个组件里需要打开一个弹窗

一般写法

是在每个单元格自定义组件里引用弹窗组件。

缺点

  • 表格渲染会创建很多弹窗组件实例,降低性能。

新的思路

定义一个函数,在执行函数时,实时创建弹窗组件并挂载,函数返回一个promisepromise在弹窗组件点击确定的时候resolve(data),点击关闭的时候reject

消费示例,其中open-dialog.js在下面定义

  import openDialog from './open-dialog.js'
  
  // 点击打开弹窗,获取弹窗里的数据
  const handleClick = async ()=>{
      const data = await openDialog();
      // 使用返回的data数据进行后续操作
      // ……
  }

优点

  • 按需实例化弹窗组件,只生成一个实例,提升性能。
  • 函数式调用,返回promise,promise返回数据,可以使用同步编码逻辑,无需在主组件的template中引入弹窗组件,也不用写回调事件

主要代码

主体js文件,接收options,传递options、confirm事件、close事件给创建的弹窗组件实例。返回一个promise

// open-dialog.js

import ElementPlus from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import Dialog from './dialog.vue'

const openDilaog = (options) => {
  return new Promise((resolve, reject) => {
    // 创建一个dom,作为挂载点
    const div = document.createElement('div')
    document.body.appendChild(div)
    
    // 创建弹窗组件应用实例
    let app = createApp(Dialog, {
      // 作为props传递给Dialog组件
      ...options,
      // 等价于 <Dialog @confirm="xxxx" />
      onConfirm: (data) => {
        // 返回弹窗组件内部要传出来的数据
        resolve(data)
        // 卸载应用实例
        app?.unmount()
        // 移除挂载点dom
        document.body.removeChild(div)
        // 释放应用实例占用的内存
        app = null
      },
      // 等价于 <Dialog @close="xxxx" />
      onClose: (data) => {
        // 弹窗关闭
        reject(data)
        app?.unmount()
        document.body.removeChild(div)
        app = null
      }
    })
    
    // vue3的应用实例都是隔离的,所以这里要单独安装element-plus插件,否则Dialog里的element组件不会渲染,如果有用到element-plus的icon组件,也同理需要安装插件,我们这里有app,参考element-plus官网文档,相信你知道怎么安装icon组件
    app.use(ElementPlus, { locale: zhCn })
    // 挂载应用实例,并返回组件实例
    const vm = app.mount(div)
    // 手动打开弹窗,如果Dialog组件内的visible默认是true,可以不需要这一步。
    vm.open()
  })
}

// 这么写,方便import的时候兼容各种写法
export {openDilaog, openDilaog as default}

dialog组件代码,定义open函数,confirmclose事件

<!-- dialog.vue -->

<template>
  <el-dialog v-model="dialogVisible" title="Tips" width="500">
    <span>This is a dialog</span>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="handleCancel">取消</el-button>
        <el-button type="primary" @click="handleConfirm"> 确认 </el-button>
      </div>
    </template>
  </el-dialog>
</template>

<script setup>
import { ref } from 'vue'

const dialogVisible = ref(false)
const emit = defineEmits(['confirm', 'close'])

const open = () => {
  dialogVisible.value = true
}
const handleConfirm = () => {
  emit('confirm', { data: 'confirm' })
  dialogVisible.value = false
}

const handleCancel = () => {
  emit('close', { data: 'close' })
  dialogVisible.value = false
}

defineExpose({ open })
</script>

使用

  import openDialog from './open-dialog.js'
  
  // 点击打开弹窗,获取弹窗里的数据
  const handleClick = async ()=>{
      const data = await openDialog();
  }