重复写弹窗很烦 我用命令的方式创建它

124 阅读1分钟

前言

代码写的不咋样, 各位大佬轻喷

由于写弹窗页面的逻辑基本是一样的, 总是重复那些搬dialog, 绑定visible的操作, 变化的只是弹窗里面的内容, 所以我思考了一下用命令的方式创建弹窗, 只用传入props与组件, 解决这些繁琐的步骤,只关注主要业务代码的编写

优点

命令式创建dialog摆脱了繁琐的重复搬dialog以及绑定visible的工作, 只需把主要组件引入即可使用

缺点

传入的组件需要expose方法, 弹窗才能使用, 比如说要做表单校验则需要与组件约定好, 暴露一个固定的方法名

实现

我思考的是用插件的形式实现, 在app.config.globalProperties上创建全局的$createDialog方法, 然后写一个固定的弹窗模板组件, 内部定义好visible变量为true, 主业务组件通过props动态传入并渲染, 接着通过createVNode(创建虚拟节点)和render(渲染虚拟节点)方法生成真实的dom, 最后通过document.body.appendChild添加到页面上

在关闭弹窗的时候调用销毁的方法, 通过container.parentNode.removeChild(container)把弹窗移除即可

使用

父组件 image.png

业务组件 image.png

效果 动画.gif

具体代码实现

import { createVNode, render, markRaw, ref, computed } from 'vue';
const myDialog = {
    setup(props) {
        const visible = ref(true)
        // 确定按钮loading
        const confirmLoading = ref(false)
        const componentRef = ref(null)
        const myProps = computed(() => {
            return {
                ...props,
                afterClose() {
                    props.afterClose && props.afterClose()
                    // 关闭后销毁
                    props.destory()
                },
                async onOk() {
                    try {
                        confirmLoading.value = true
                        // 与传入组件约定好暴露的方法名,可以在此进行表单验证等操作
                        if (componentRef.value.handleOk) {
                            await componentRef.value.handleOk()
                        }
                        if (props.onOk) {
                            await props.onOk()
                        }
                        visible.value = false
                    } catch (error) {
                        console.error(error)
                    } finally {
                        confirmLoading.value = false
                    }
                },
                onCancel() {
                    props.onCancel && props.onCancel()
                }
            }
        })
        return () => {
            return (
                <a-modal
                    v-model:visible={visible.value}
                    confirmLoading={confirmLoading.value}
                    {...myProps.value}
                >
                    {
                        // 渲染传入的组件
                        props.component ? <props.component ref={componentRef} {...(props.componentProps || {})}></props.component> : ''
                    }
                </a-modal >
            )
        }
    }
}
export default {
    install: (app) => {
        // app.config.globalProperties注册全局方法,相当于vue2的Vue.prototype
        app.config.globalProperties.$createDialog = function (opts) {
            //创建虚拟节点
            let vm = createVNode(myDialog);
            //创建div容器
            let container = document.createElement('div');
            // 这样做的目的是,传入的组件在这里其实已经独立于应用的Vue上下文了。为了让组件依然保持和调用方相同的Vue上下文,我这里加入了获取上下文的操作!
            vm.appContext = app._context
            //渲染虚拟节点
            render(vm, container);
            //将创建好的div元素添加到body元素内
            document.body.appendChild(container);
            //设置dialog的props选项内部的值
            Object.assign(vm.component.props, {
                ...opts,
                // 将传入组件标记,使其不可以被转化为代理对象,返回该组件本身。
                component: opts.component ? markRaw(opts.component) : null,
                destory() {
                    render(null, container);
                    container.parentNode?.removeChild(container)
                }
            })

        };
    },
};