基于Element-plus组件的二次封装-XDialog\XConfirm\XMessage

2,130 阅读2分钟

背景:

公司的前端技术框架向前看,Vue2.x ---> Vue3.0。业务方希望保留原有的UI

准备工作

第三方组件注册 - XDialog

  • 组件注入Vue实例时 必须要有 install 方法 (也就是在根组件创建时,要进行一下use)
    // main.js
    import { createApp } from 'vue'
    ...
    import XDialog from '@/components/XDialog/index'
    
    createApp(App)
        .use(XDialog) // 注入组件
        .use(...)
    
    // @/components/XDialog/index.js
    
    ... 此处先省略个 20几行 
    
    // 导出时,给出 install 方法 
    export default {
        install: (App) => {
            // 为什么使用 provide / inject 的方式去做呢<尤雨溪推荐的,你问他呗>
            App.provide('XDialong', MyDialog) 
            // 当然也可以使用下面的方式 (还是告诉你吧:因为我希望在script中进行调用,不写在template里)
            // App.component(XDialog.name, XDialog) 注意避坑 -> 组件内部一定要写name属性
        }
    }
    
  • 组件调用

    <script lang="ts">
    import { ...., inject } from 'vue';
    export default defineComponent({
        setup() {
            // 这里的类型推断我一直不会整,希望有大佬指点一下 🤷‍♀️🤷‍♀️
            const $Dialog: Function = inject('XDialog') || noop;
            const addSomethins = () => {
                // 注意观察一下,前面几个参数都是和Element-plus中的dialog参数保持一致的
                // 更多的自定义配置请继续往下看啊🤣
                $Dialog({
                    title: '新 增',
                    width: '500px',
                    closeOnClickModal: false,
                    contentComponent: defineAsyncComponent(() => import('./Form.vue')),
                    confirm: (component: Component) => {
                        // 这里的component是异步组件的一个引用,方便获取组件参数
                        console.log(component) 
                        // 如果在此函数中返回一个false 将不会让弹窗关闭
                    },
                    cancel: () => {}
                })
            }
        }
    })
    
    </script>

  • 组件二次封装的核心实现 🤞🤞🤞🤞

特别说明一下这里的实现注册完全是看了 element-plus 的组件注册去实现的,相当于拿了别人的东西,再自己装牛*。 面对疾风吧。

    // @/components/XDialog/index.js
    // 导入 composition-api 为什么有compsition-api和为什么要这么设计 请自行百度!
    import { h, render } from 'vue'
    import XDialog from 'XDialog.vue'
    
    const getContainer = () => document.createElement('div')
    
    const initInstance = (props, container) => {
        const vnode = h(XDialog, props) // 创建 VNode 虚拟dom
        render(vnode, container) // 渲染到container中 此时并没有挂到body
        const $el = container.firstElementChild
        document.body.appendChild($el) // 加入到body
        // 返回当前的 el 以及实例 
        return {
            $el,
            instance: vnode.component
        }
    }
    
    const show = (options) => {
        const container = getContainer()
        const { instance, $el } = initInstance(options, container)
        const vm = instance.proxy // 这儿的vm是 instance 的 proxy -- h 生成时是这样设计的(我也不知这样解释是不是完全正确,源码没认真读...)
        for (const prop in options) {
            // 遍历传递 props (不能将组件内部的私有属性覆盖了)
            if (options.hasOwnPrroperty(prop) && !vm.$props.hasOwnProperty(prop)) {
                vm[prop] = options[prop]
            }
        }
        vm.visible = true // 这里的visible是控制dialog显示隐藏的字段-也支持自定义
        return {
            $el,
            vm,
            instance
        }
    }
    
    function MyDialog (options) {
        const context = show({
            ...options,
            // 这里的 closeCallback 是在内层当dialog关闭时,将当前的 dialog 以及其父级div进行移除
            closeCallback() { 
                context.vm.visible = false
                setTimeout(() => {
                    document.body.removeChild(context.$el)
                }, 150)
            }
        })
    }
    
     // 导出时,给出 install 方法 
    export default {
        install: (App) => {
            // 为什么使用 provide / inject 的方式去做呢<尤雨溪推荐的,你问他呗>
            App.provide('XDialong', MyDialog) 
            // 当然也可以使用下面的方式 (还是告诉你吧:因为我希望在script中进行调用,不写在template里)
            // App.component(XDialog.name, XDialog) 注意避坑 -> 组件内部一定要写name属性
        }
    }
    

  • XDialog.vue关键函数
  1. close 函数
    setup(props) {
        ...
        const close = () => props.colseCallback && props.colseCallback()
    }
  1. confirm 函数
    setup(props) {
        ...
        // 确认函数的执行以及组件实例的暴露
        const confirmHandler = async () => {
            if (typeof props.confirm !== 'function' ||
                (await props.confirm(dialogRef.value) !== false)
            ) {
                close()
            }
        }
    }

第三方组件注册 - XConfirm

  • 组件注册以及组件调用与XDialog都是大同小异的 也是基于el-dialog组件进行改造

唯独在组件的 show 方法做了手脚

  • 直接给出相关的代码
    // @/components/XConfirm/index
    ... 
    async function MyConfirm (options) {
        let ctx
        // 为什么只有用到resolve, 因为不想外层的链式调用还写一个catch
        return new Promise(resolve => {
            ctx = show({
                ...options,
                confirm() {
                    resolve(true)
                },
                cancel() {
                    resolve(false)
                },
                closeCallback() {
                    ctx.vm.visible = false
                    setTimeout(() => {
                        document.body.removeChild(ctx.$el)
                    }, 150)
                }
            })
        })
    }
    export default {
        install (App) {
            App.provide('XConfirm', MyConfirm)
        }
    }
    
    // 业务代码调用时
    const $confirm = inject('XConfirm')
    const doSomthing = () => {
        $confirm({
            title: '提 示',
            message: '确认今晚吃鸡吗?',
            width: '400px',
            top: '20vh',
            closeOnClickModal: false
        }).then(async (bool) => {
            if (bool) {
                .....Do your shit
            }
        })
    }

第三方组件注册 - XMessage

  • 直接上码吧 --- 你可以自己拓展了
    // @/components/XMessage/index
    ....
    function MyMessage (options) {
        const delay = options.delay || 2000
        const ctx = show({ ...options })
        setTimeout(() => {
            ctx.vm.visible = false
            setTimeout(() => {
                document.body.removeChild(ctx.$el)
            }, 150)
        }, delay)
        
    }
    
    export default {
        install(App) {
           App.provide('XMessage', MyMessage)
        }
    }

最后:一起打球吗?

9aacc47a4a8d2baa4c284a433a0d725c.gif