前言
代码写的不咋样, 各位大佬轻喷
由于写弹窗页面的逻辑基本是一样的, 总是重复那些搬dialog, 绑定visible的操作, 变化的只是弹窗里面的内容, 所以我思考了一下用命令的方式创建弹窗, 只用传入props与组件, 解决这些繁琐的步骤,只关注主要业务代码的编写
优点
命令式创建dialog摆脱了繁琐的重复搬dialog以及绑定visible的工作, 只需把主要组件引入即可使用
缺点
传入的组件需要expose方法, 弹窗才能使用, 比如说要做表单校验则需要与组件约定好, 暴露一个固定的方法名
实现
我思考的是用插件的形式实现, 在app.config.globalProperties上创建全局的$createDialog方法, 然后写一个固定的弹窗模板组件, 内部定义好visible变量为true, 主业务组件通过props动态传入并渲染, 接着通过createVNode(创建虚拟节点)和render(渲染虚拟节点)方法生成真实的dom, 最后通过document.body.appendChild添加到页面上
在关闭弹窗的时候调用销毁的方法, 通过container.parentNode.removeChild(container)把弹窗移除即可
使用
父组件
业务组件
效果
具体代码实现
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)
}
})
};
},
};