场景
有些时候我们的页面是有很多的弹窗
如果我们把这些弹窗都写html中会有一大坨
因此:我们需要把弹窗封装成命令式的形式
命令式弹窗
- // 封装的弹窗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)}
复制代码- //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中没有注册组件。
因此会警告,页面渲染不出来。- // 我重新创建了一个app,这个app中没有注册 element-plus 组件。const app = createApp(dialog)
复制代码 现在我们重新注册element-plus组件。
准确的说:我们要注册 childTest.vue 组件使用到的东西
给新创建的app应用注册childTest组件使用到的东西
我们将会在这个命令式弹窗中重新注册需要使用到的组件- // 封装的弹窗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函数中的插槽来创建底部的取消按钮- // 封装的弹窗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,点击其他地方的关闭
关闭弹窗正确销毁相关组件
- // 封装的弹窗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)}
复制代码
点击确认按钮时验证规则
有些时候,我们弹窗中的表单是需要进行规则校验的。
我们下面来实现这个功能点
传递的组件- // 封装的弹窗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要暴露需要的方法
如何把表单中的数据暴露出去
可以通过回调函数的方式把数据暴露出去哈。- // 封装的弹窗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)}
复制代码
点击确定时,业务完成后关闭弹窗
现在想要点击确定,等业务处理完成之后,才关闭弹窗。
需要在使用完成业务的时候返回一个promise,让封装的弹窗调用这个promise
这样就可以知道什么时候关闭弹窗了- // 封装的弹窗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)}
复制代码
优化业务组件
- // 封装的弹窗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,验证不通过会也会触发卸载弹窗
2,callbackResult.finally是不合适的
3.
最终的代码
- // 封装的弹窗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)}
复制代码 微信 本文版权归作者所有,欢迎转载,未经作者同意须保留此段声明,在文章页面明显位置给出原文连接
如果文中有什么错误,欢迎指出。以免更多的人被误导。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |