找回密码
 立即注册
首页 业界区 业界 vue使用h函数封装dialog组件,以命令的形式使用dialog组 ...

vue使用h函数封装dialog组件,以命令的形式使用dialog组件

坟菊 2026-1-14 03:15:00
场景

有些时候我们的页面是有很多的弹窗
如果我们把这些弹窗都写html中会有一大坨
因此:我们需要把弹窗封装成命令式的形式
命令式弹窗
  1. // 使用弹窗的组件      点击弹窗  
复制代码
  1. // 封装的弹窗import { createApp, h } from "vue";import { ElDialog } from "element-plus";export function renderDialog(component:any,props:any, modalProps:any){ const dialog  = h(    ElDialog,   // 模态框组件    {      ...modalProps, // 模态框属性      modelValue:true, // 模态框是否显示    }, // 因为是模态框组件,肯定是模态框的属性    {      default:()=>h(component, props ) // 插槽,el-dialog下的内容    }  ) console.log(dialog)  // 创建一个新的 Vue 应用实例。这个应用实例是独立的,与主应用分离。  const app = createApp(dialog)  const div = document.createElement('div')  document.body.appendChild(div)  app.mount(div)}
复制代码
  1. //childTest.vue 组件      It's a modal Dialog                                                         
复制代码

为啥弹窗中的表单不能够正常展示呢?

在控制台会有下面的提示信息:
Failed to resolve component:
el-form If this is a native custom element,
make sure to exclude it from component resolution via compilerOptions.isCustomElement
翻译过来就是
无法解析组件:el-form如果这是一个原生自定义元素,
请确保通过 compilerOptions.isCustomElement 将其从组件解析中排除

其实就是说:我重新创建了一个新的app,这个app中没有注册组件。
因此会警告,页面渲染不出来。
  1. // 我重新创建了一个app,这个app中没有注册 element-plus 组件。const app = createApp(dialog)
复制代码
现在我们重新注册element-plus组件。
准确的说:我们要注册 childTest.vue 组件使用到的东西
给新创建的app应用注册childTest组件使用到的东西

我们将会在这个命令式弹窗中重新注册需要使用到的组件
  1. // 封装的弹窗import { createApp, h } from "vue";import { ElDialog } from "element-plus";// 引入组件和样式import ElementPlus from "element-plus";// import "element-plus/dist/index.css";export function renderDialog(component:any,props:any, modalProps:any){ const dialog  = h(    ElDialog,   // 模态框组件    {      ...modalProps, // 模态框属性      modelValue:true, // 模态框显示    }, // 因为是模态框组件,肯定是模态框的属性    {      default:()=>h(component, props ) // 插槽,el-dialog下的内容    }  ) console.log(dialog)  // 创建一个新的 Vue 应用实例。这个应用实例是独立的,与主应用分离。  const app = createApp(dialog)  // 在新实例中注册 Element Plus, 这弹窗中的组件就可以正常显示了  app.use(ElementPlus);  const div = document.createElement('div')  document.body.appendChild(div)  app.mount(div)}
复制代码

现在我们发现可以正常展示弹窗中的表单了。因为我们注册了element-plus组件。
但是我们发现又发现了另外一个问题。
弹窗底部没有取消和确认按钮。
需要我们再次通过h函数来创建
关于使用createApp创建新的应用实例

在Vue 3中,我们可以使用 createApp 来创建新的应用实例
但是这样会创建一个完全独立的应用
它不会共享主应用的组件、插件等。
因此我们需要重新注册
弹窗底部新增取消和确认按钮

我们将会使用h函数中的插槽来创建底部的取消按钮
  1. // 封装的弹窗import { createApp, h } from "vue";import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";import ElementPlus from "element-plus";export function renderDialog(component: any, props: any, modalProps: any) {  // 创建弹窗实例  const dialog = h(    ElDialog,    {      ...modalProps,      modelValue: true,    },    {      // 主要内容插槽      default: () => h(component, props),      // 底部插槽      footer:() =>h(        'div',        { class: 'dialog-footer' },        [          h(            ElButton,             {              onClick: () => {                console.log('取消')              }            },            () => '取消'          ),          h(            ElButton,            {               type: 'primary',              onClick: () => {                console.log('确定')              }            },            () => '确定'          )        ]      )    }  );  // 创建一个新的 Vue 应用实例。这个应用实例是独立的,与主应用分离。  const app = createApp(dialog)  // 在新实例中注册 Element Plus, 这弹窗中的组件就可以正常显示了  app.use(ElementPlus);  const div = document.createElement('div')  document.body.appendChild(div)  app.mount(div)}
复制代码

点击关闭弹窗时,需要移除之前创建的div

卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div。
2个地方需要移除:1,点击确认按钮。 2,点击其他地方的关闭

关闭弹窗正确销毁相关组件
  1. // 封装的弹窗import { createApp, h } from "vue";import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";import ElementPlus from "element-plus";export function renderDialog(component: any, props: any, modalProps: any) {  console.log('111')  // 创建弹窗实例  const dialog = h(    ElDialog,    {      ...modalProps,      modelValue: true,      onClose: ()=> {        console.log('关闭的回调')        app.unmount() // 这样卸载会让动画消失        // 卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div        document.body.removeChild(div)      }    },    {      // 主要内容插槽      default: () => h(component, props),      // 底部插槽      footer:() =>h(        'div',        {           class: 'dialog-footer',                 },        [          h(            ElButton,             {              onClick: () => {                console.log('点击取消按钮')                // 卸载一个已挂载的应用实例。卸载一个应用会触发该应用组件树内所有组件的卸载生命周期钩子。                app.unmount() // 这样卸载会让动画消失                // 卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div                document.body.removeChild(div)              }            },            () => '取消'          ),          h(            ElButton,            {               type: 'primary',              onClick: () => {                console.log('确定')              }            },            () => '确定'          )        ]      )    }  );  // 创建一个新的 Vue 应用实例。这个应用实例是独立的,与主应用分离。  const app = createApp(dialog)  // 在新实例中注册 Element Plus, 这弹窗中的组件就可以正常显示了  app.use(ElementPlus);  // 这个div元素在在销毁应用时需要被移除哈  const div = document.createElement('div')  document.body.appendChild(div)  app.mount(div)}
复制代码

点击确认按钮时验证规则

有些时候,我们弹窗中的表单是需要进行规则校验的。
我们下面来实现这个功能点
传递的组件
  1.                                                                                                                 -                                                                  Sponsorship        Venue                          
复制代码
  1. // 封装的弹窗import { createApp, h, ref } from "vue";import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";import ElementPlus from "element-plus";export function renderDialog(component: any, props: any, modalProps: any) {  const instanceElement = ref()  console.log('111', instanceElement)   // 创建弹窗实例  const dialog = h(    ElDialog,    {      ...modalProps,      modelValue: true,      onClose: ()=> {        console.log('关闭的回调')        app.unmount() // 这样卸载会让动画消失        // 卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div        document.body.removeChild(div)      }    },    {      // 主要内容插槽,这里的ref必须接收一个ref      default: () => h(component, {...props, ref: instanceElement}),      // 底部插槽      footer:() =>h(        'div',        {           class: 'dialog-footer',                 },        [          h(            ElButton,             {              onClick: () => {                console.log('点击取消按钮')                // 卸载一个已挂载的应用实例。卸载一个应用会触发该应用组件树内所有组件的卸载生命周期钩子。                app.unmount() // 这样卸载会让动画消失                // 卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div                document.body.removeChild(div)              }            },            () => '取消'          ),          h(            ElButton,            {               type: 'primary',              onClick: () => {                instanceElement?.value?.submitForm().then((res:any) =>{                  console.log('得到的值',res)                })                console.log('确定')              }            },            () => '确定'          )        ]      )    }  );  // 创建一个新的 Vue 应用实例。这个应用实例是独立的,与主应用分离。  const app = createApp(dialog)  // 在新实例中注册 Element Plus, 这弹窗中的组件就可以正常显示了  app.use(ElementPlus);  // 这个div元素在在销毁应用时需要被移除哈  const div = document.createElement('div')  document.body.appendChild(div)  app.mount(div)}
复制代码

关键的点:通过ref拿到childTest组件中的方法,childTest要暴露需要的方法
如何把表单中的数据暴露出去

可以通过回调函数的方式把数据暴露出去哈。
  1. // 封装的弹窗import { createApp, h, ref } from "vue";import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";import ElementPlus from "element-plus";export function renderDialog(component: any, props: any, modalProps: any, onConfirm: (data: any) => any ) {  // 第4个参数是回调函数  const instanceElement = ref()  console.log('111', instanceElement)   // 创建弹窗实例  const dialog = h(    ElDialog,    {      ...modalProps,      modelValue: true,      onClose: ()=> {        console.log('关闭的回调')        app.unmount() // 这样卸载会让动画消失        // 卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div        document.body.removeChild(div)      }    },    {      // 主要内容插槽,这里的ref必须接收一个ref      default: () => h(component, {...props, ref: instanceElement}),      // 底部插槽      footer:() =>h(        'div',        {           class: 'dialog-footer',                 },        [          h(            ElButton,             {              onClick: () => {                console.log('点击取消按钮')                // 卸载一个已挂载的应用实例。卸载一个应用会触发该应用组件树内所有组件的卸载生命周期钩子。                app.unmount() // 这样卸载会让动画消失                // 卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div                document.body.removeChild(div)              }            },            () => '取消'          ),          h(            ElButton,            {               type: 'primary',              onClick: () => {                // submitForm 调用表单组件中需要验证或者暴露出去的数据                instanceElement?.value?.submitForm().then((res:any) =>{                  console.log('得到的值',res)                  // 验证通过后调用回调函数传递数据, 如验证失败,res 的值有可能是一个false。                  onConfirm(res)                  // 怎么把这个事件传递出去,让使用的时候知道点击了确认并且知道验证通过了                }).catch((error: any) => {                  // 验证失败时也可以传递错误信息                  console.log('验证失败', error)                })                console.log('确定')              }            },            () => '确定'          )        ]      )    }  );  // 创建一个新的 Vue 应用实例。这个应用实例是独立的,与主应用分离。  const app = createApp(dialog)  // 在新实例中注册 Element Plus, 这弹窗中的组件就可以正常显示了  app.use(ElementPlus);  // 这个div元素在在销毁应用时需要被移除哈  const div = document.createElement('div')  document.body.appendChild(div)  app.mount(div)}
复制代码
  1.       点击弹窗  
复制代码

点击确定时,业务完成后关闭弹窗

现在想要点击确定,等业务处理完成之后,才关闭弹窗。
需要在使用完成业务的时候返回一个promise,让封装的弹窗调用这个promise
这样就可以知道什么时候关闭弹窗了
  1. // 封装的弹窗import { createApp, h, ref } from "vue";import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";import ElementPlus from "element-plus";export function renderDialog(component: any, props: any, modalProps: any, onConfirm: (data: any) => any ) {  // 第4个参数是回调函数  const instanceElement = ref()  console.log('111', instanceElement)   // 创建弹窗实例  const dialog = h(    ElDialog,    {      ...modalProps,      modelValue: true,      onClose: ()=> {        console.log('关闭的回调')        app.unmount() // 这样卸载会让动画消失        // 卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div        document.body.removeChild(div)      }    },    {      // 主要内容插槽,这里的ref必须接收一个ref      default: () => h(component, {...props, ref: instanceElement}),      // 底部插槽      footer:() =>h(        'div',        {           class: 'dialog-footer',                 },        [          h(            ElButton,             {              onClick: () => {                console.log('点击取消按钮')                // 卸载一个已挂载的应用实例。卸载一个应用会触发该应用组件树内所有组件的卸载生命周期钩子。                app.unmount() // 这样卸载会让动画消失                // 卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div                document.body.removeChild(div)              }            },            () => '取消'          ),          h(            ElButton,            {               type: 'primary',              onClick: () => {                // submitForm 调用表单组件中需要验证或者暴露出去的数据                instanceElement?.value?.submitForm().then((res:any) =>{                  console.log('得到的值',res)                  // 验证通过后调用回调函数传递数据,如验证失败,res 的值有可能是一个false。                  const callbackResult = onConfirm(res);                  // 如果回调函数返回的是 Promise,则等待业务完成后再关闭弹窗                  if (callbackResult instanceof Promise) {                    // 注意这里的finally,这样写在服务出现异常的时候会有问题,这里是有问题的,需要优化                    // 注意这里的finally,这样写在服务出现异常的时候会有问题,这里是有问题的,需要优化                    callbackResult.finally(() => {                       // 弹窗关闭逻辑                      app.unmount()                      document.body.removeChild(div)                    });                  } else {                    // 如果不是 Promise,立即关闭弹窗                    app.unmount()                    document.body.removeChild(div)                  }                }).catch((error: any) => {                  // 验证失败时也可以传递错误信息                  console.log('验证失败', error)                })              }            },            () => '确定'          )        ]      )    }  );  // 创建一个新的 Vue 应用实例。这个应用实例是独立的,与主应用分离。  const app = createApp(dialog)  // 在新实例中注册 Element Plus, 这弹窗中的组件就可以正常显示了  app.use(ElementPlus);  // 这个div元素在在销毁应用时需要被移除哈  const div = document.createElement('div')  document.body.appendChild(div)  app.mount(div)}
复制代码
  1.       点击弹窗  
复制代码

优化业务组件
  1. // 封装的弹窗import { createApp, h, ref } from "vue";import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";import ElementPlus from "element-plus";export function renderDialog(component: any, props: any, modalProps: any, onConfirm: (data: any) => any ) {  // 关闭弹窗,避免重复代码  const closeDialog = () => {    // 成功时关闭弹窗    app.unmount();    // 检查div是否仍然存在且为body的子元素,否者可能出现异常    if (div && div.parentNode) {      document.body.removeChild(div)    }  }  // 第4个参数是回调函数  const instanceElement = ref()  console.log('111', instanceElement)   // 创建弹窗实例  const dialog = h(    ElDialog,    {      ...modalProps,      modelValue: true,      onClose: ()=> {        console.log('关闭的回调')        app.unmount() // 这样卸载会让动画消失        // 卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div        document.body.removeChild(div)      }    },    {      // 主要内容插槽,这里的ref必须接收一个ref      default: () => h(component, {...props, ref: instanceElement}),      // 底部插槽      footer:() =>h(        'div',        {           class: 'dialog-footer',                 },        [          h(            ElButton,             {              onClick: () => {                console.log('点击取消按钮')                // 卸载一个已挂载的应用实例。卸载一个应用会触发该应用组件树内所有组件的卸载生命周期钩子。                app.unmount() // 这样卸载会让动画消失                // 卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div                document.body.removeChild(div)              }            },            () => '取消'          ),          h(            ElButton,            {               type: 'primary',              onClick: () => {                // submitForm 调用表单组件中需要验证或者暴露出去的数据                instanceElement?.value?.submitForm().then((res:any) =>{                  console.log('得到的值',res)                  // 验证通过后调用回调函数传递数据,如验证失败,res 的值有可能是一个false。                  const callbackResult = onConfirm(res);                  // 如果回调函数返回的是 Promise,则等待业务完成后再关闭弹窗                  if (callbackResult instanceof Promise) {                                        callbackResult.then(() => {                      if(res){                        console.log('111')                        closeDialog()                      }                    }).catch(error=>{                      console.log('222')                      console.error('回调函数执行出错,如:网络错误', error);                      // 错误情况下也关闭弹窗                      closeDialog()                    });                  } else {                    // 如果不是 Promise,并且验证时通过了的。立即关闭弹窗                    console.log('333', res)                    if(res){                      closeDialog()                    }                  }                }).catch((error: any) => {                  console.log('44444')                  // 验证失败时也可以传递错误信息                  console.log('验证失败', error)                })              }            },            () => '确定'          )        ]      )    }  );  // 创建一个新的 Vue 应用实例。这个应用实例是独立的,与主应用分离。  const app = createApp(dialog)  // 在新实例中注册 Element Plus, 这弹窗中的组件就可以正常显示了  app.use(ElementPlus);  // 这个div元素在在销毁应用时需要被移除哈  const div = document.createElement('div')  document.body.appendChild(div)  app.mount(div)}
复制代码
  1.       点击弹窗  
复制代码
眼尖的小伙伴可能已经发现了这一段代码。
1,验证不通过会也会触发卸载弹窗
2,callbackResult.finally是不合适的
3.


最终的代码
  1. // 封装的弹窗import { createApp, h, ref } from "vue";import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";import ElementPlus from "element-plus";export function renderDialog(component: any, props: any, modalProps: any, onConfirm: (data: any) => any ) {  // 关闭弹窗,避免重复代码  const closeDialog = () => {    // 成功时关闭弹窗    app.unmount();    // 检查div是否仍然存在且为body的子元素,否者可能出现异常    if (div && div.parentNode) {      document.body.removeChild(div)    }  }  // 第4个参数是回调函数  const instanceElement = ref()  console.log('111', instanceElement)   const isLoading = ref(false)  // 创建弹窗实例  const dialog = h(    ElDialog,    {      ...modalProps,      modelValue: true,      onClose: ()=> {        isLoading.value = false        console.log('关闭的回调')        app.unmount() // 这样卸载会让动画消失        // 卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div        document.body.removeChild(div)      }    },    {      // 主要内容插槽,这里的ref必须接收一个ref      default: () => h(component, {...props, ref: instanceElement}),      // 底部插槽,noShowFooterBool是true,不显示; false的显示底部       footer: props.noShowFooterBool ? null : () =>h(        'div',        {           class: 'dialog-footer',        },        [          h(            ElButton,             {              onClick: () => {                console.log('点击取消按钮')                // 卸载一个已挂载的应用实例。卸载一个应用会触发该应用组件树内所有组件的卸载生命周期钩子。                app.unmount() // 这样卸载会让动画消失                // 卸载的同时需要把我们创建的div元素移除,否则页面上会出现很多div                document.body.removeChild(div)              }            },            () => props.cancelText || '取消'          ),          h(            ElButton,            {               type: 'primary',              loading: isLoading.value,              onClick: () => {                isLoading.value = true                // submitForm 调用表单组件中需要验证或者暴露出去的数据                instanceElement?.value?.submitForm().then((res:any) =>{                  if(!res){                    isLoading.value = false                  }                  console.log('得到的值',res)                  // 验证通过后调用回调函数传递数据,如验证失败,res 的值有可能是一个false。                  const callbackResult = onConfirm(res);                  // 如果回调函数返回的是 Promise,则等待业务完成后再关闭弹窗                  if (callbackResult instanceof Promise) {                     callbackResult.then(() => {                      if(res){                        console.log('111')                        closeDialog()                      }else{                        isLoading.value = false                      }                    }).catch(error=>{                      console.log('222')                      console.error('回调函数执行出错,如:网络错误', error);                      // 错误情况下也关闭弹窗                      closeDialog()                    });                  } else {                    // 如果不是 Promise,并且验证时通过了的。立即关闭弹窗                    console.log('333', res)                    if(res){                      closeDialog()                    }else{                      isLoading.value = false                    }                  }                }).catch((error: any) => {                  console.log('44444')                   isLoading.value = false                  // 验证失败时也可以传递错误信息                  console.log('验证失败', error)                })              }            },            () => props.confirmText ||  '确定'          )        ]      )     }  );  // 创建一个新的 Vue 应用实例。这个应用实例是独立的,与主应用分离。  const app = createApp(dialog)  // 在新实例中注册 Element Plus, 这弹窗中的组件就可以正常显示了  app.use(ElementPlus);  // 这个div元素在在销毁应用时需要被移除哈  const div = document.createElement('div')  document.body.appendChild(div)  app.mount(div)}
复制代码
  1.       点击弹窗  
复制代码
  1.                                                                                                                 -                                                                    Sponsorship        Venue                          
复制代码
                                                                                                                       
                                                微信                                                                                                本文版权归作者所有,欢迎转载,未经作者同意须保留此段声明,在文章页面明显位置给出原文连接
                        如果文中有什么错误,欢迎指出。以免更多的人被误导。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册