解决vue组件中大量dialog带来的变量/方法污染

447 阅读2分钟

你是否如下困惑

  1. 再表格界面有大量的操作项目,引用大量的dialog,每个diaolog要设置他的可见属性,比如
export default {
    data(){
        return {
            dialog1: false,
            dialog2: false,
            dialog3: false,
            dialog4: false,
        }
    }
}
  1. 不仅如此你还需要多个方法钩子来处理打开和关闭的变量切换
export default {
    method: {
        dialog1Open(){ this.dilaog1 = true },
        dialog1Close(){ this.dilaog1 = false },
        dialog2Open(){ this.dilaog2 = true },
        dialog2Close(){ this.dilaog2 = false },
    }
}
  1. 还可能需要为每个弹窗内维护一份自己的数据
  2. 在打开关闭对话框的瞬间,还要做数据的相关处理,比如关闭对话框意味着不修改,则需要进行值的还原或相关处理

我们希望对话框应该是一个完全“模态”的一个组件,在多数的页面中,负责的对话框往往是用来做数据的处理,比如表单,我们期望他在工作时尽量减少对上下文环境的关注,而只聚焦在数据的进和出本身.

使用动态创建来分离dialog文件

上面提到的绝大部分问题都是因为当下的对话框和业务组件写在一起导致的,尽管我们可以将对话框的业务封装出去,但是还是免不了第一点和第二点的影响,我们这里希望的时完全拆出去,仅作数据传输,我们希望有如下的调用逻辑

/**
 * 相应编辑规则
 */
async function handleEditRule(){
    this.rule = await modalLoader(Dialog, this.rule);
    // 此处 Dialog 是一个 Vue组件
    // this.rule 是传递给 Dialog 的组件数据
    // 在 Dialog 进行编辑调成完毕后,如果用户点击了确认,则应该将编辑成功的数据返回回来
    // 我们不希望再我们的组件中额外的编写任何html片段,以及增加相关的变量,回调狗子等
}

上面的代码中,并不需要dialog组件写入到template中,同时,面向过程的编程方案,让click动作响应起来更自然更加舒服

modal代码应为

import { createApp } from 'vue'

type VueComponent = Parameters<typeof createApp>[0]

export default async function modalLoader<T = any>(Dialog: VueComponent, config = {}): Promise<T>{


    return new Promise((resolve, reject) => {
        
        
        
        const div = document.createElement('div');
        document.body.appendChild(div);

        const destory = ()=>{
            inst.unmount()
            document.body.removeChild(div);
            delete div;
            delete inst;
        }
    
        const inst = createApp(Dialog, {
            ...config,
            onOk: (val: any) => {
                destory()
                resolve(val)
            },
            onClose: () => {
               destory()
            },
            onReject: (e: any) => {
                destory()
                reject(e)
            }
        });
        
        inst.mount(div);
    })
}

该方案的逻辑

  1. 新建一个dom,并插入到body
  2. 在这个dom上面初始化弹窗组件,初始化时注入对话框关闭的相关钩子
  3. 通过promise和调用处进行互联和传值

对应的我们的模态对话框应该像如下

<template>
    <el-dialog :model-value="true" :title="title" center>
        <el-input v-model="input" placeholder="Please input" />
        <template #footer>
            <span class="dialog-footer">
                <el-button @click="onClose">Cancel</el-button>
                <el-button type="primary" @click="onOk(input)"> Confirm </el-button>
            </span>
        </template>
    </el-dialog>
</template>

<script setup>
import { defineProps, ref } from "vue";

const input = ref("");
const props = defineProps({
    // 模态对话框的自定义传参内容
    title: String,

    // 工具链路注入的方法, 这些方法将会决定模态对话框如何返回
    onOk: Function,
    onClose: Function,
    onReject: Function,
});
</script>

而在主业务侧,代码代码将省去HTML片段的烦恼,只需要如下内容

<template>
    <el-button @click="openDialog1"> 打开对话框1 </el-button>
</template>
<script setup>
async openDialog1() {
    const name = await modalLoader(dialog, {
        title: "请输入您的姓名",
    });
    ElMessage("输入了" + name);
}
</setup>

附上代码地址 vigorous-sunset-l21hst - CodeSandbox

最后

方案应该还有一些不够完善的地方,欢迎交流,提出问题,共同进步,感恩