vue3: 多 Modal 框业务逻辑代码优化
背景介绍
开发中经常会遇到如下场景.
点击按钮, 通过变量 visible 控制弹框的显示和隐藏:
export default defineComponent({
setup() {
const visible = ref(false);
return {
visible,
};
},
render() {
return (
<>
<Button
onClick={() => {
this.visible = true;
}}
>
显示弹框
</Button>
<MyModal
visible={visible}
onClose={() => {
this.visible = false;
}}
/>
</>
);
},
});
复制代码
但是随着业务不断的迭代, 一个页面上可能会存在多个 Modal 框, 此时我们该如何更优雅的处理呢? 如:
export default defineComponent({
setup() {
const visible1 = ref(false);
const visible2 = ref(false);
const visible3 = ref(false);
return {
visible1,
visible2,
visible3,
};
},
render() {
return (
<>
<Button
onClick={() => {
this.visible1 = true;
}}
>
显示弹框1
</Button>
<Button
onClick={() => {
this.visible2 = true;
}}
>
显示弹框2
</Button>
<Button
onClick={() => {
this.visible2 = true;
}}
>
显示弹框3
</Button>
<MyModal1
visible={visible1}
onClose={() => {
this.visible1 = false;
}}
/>
<MyModal2
visible={visible2}
onClose={() => {
this.visible2 = false;
}}
/>
<MyModal3
visible={visible3}
onClose={() => {
this.visible3 = false;
}}
/>
</>
);
},
});
复制代码
多个 Modal 框的场景下, 这种方式显然看起来不够优雅. 代码啰嗦不够精简.
大家平时应该都是这么开发的, 问题是有没有更好的方式呢?
思路
使用声明式组件开发模式肯定会遇到这种情况.
但是如果我们能通过函数式的方式调用组件, 可能是个不错的选择. 伪代码如下:
export default defineComponent({
render() {
return (
<>
<Button
onClick={() => {
MyModal1({}).then((data) => {});
}}
>
显示弹框1
</Button>
<Button
onClick={() => {
MyModal2({}).then((data) => {});
}}
>
显示弹框2
</Button>
<Button
onClick={() => {
MyModal3({}).then((data) => {});
}}
>
显示弹框3
</Button>
</>
);
},
});
复制代码
这种方式, 至少逻辑清晰, 可读性更强. 也是我比较推荐和喜欢的一种方式, 之前用 react 的时候也封装过相应的基础组件. 现在切换到 vue3 技术栈, 尝试着再实现一遍.
如何实现
先贴代码, 基础包裹组件的实现(逻辑看代码内的注释):
import { createVNode, render, VNodeTypes } from "vue";
export async function DynamicModal(component: VNodeTypes, props: object) {
return new Promise((resolve) => {
1️⃣ // 创建父级包裹对象
const container = document.createElement("div");
2️⃣ // 创建vue Node实例, 并传递props(强制传递onClose方法)
const vm = createVNode(component, {
...props,
onClose: (params: unknown) => {
5️⃣ // 子组件销毁时调用onClose方法, 移除响应的DOM节点
document.body.removeChild(
document.querySelector(".ant-modal-root")?.parentNode as Node
);
document.body.removeChild(container);
6️⃣ // 返回子组件传递的数据
resolve(params);
},
});
3️⃣ // 把vue Node实例渲染到包裹对象中
render(vm, container);
4️⃣ // 将包裹对象插入到body中
document.body.appendChild(container);
});
}
复制代码
如何使用:
// CellModal.tsx
// 定义组件
const CellModal = defineComponent({
props: {
onClose: {
type: Function as PropType<(param: unknown) => void>,
required: true,
},
props1: {
type: String as PropType<string>,
required: true,
},
props2: {
type: Object as PropType<any>,
required: true,
},
},
setup(props) {
const visible = ref(true);
const close = () => {
visible.value = false;
props.onClose({ text: "这是子组件的数据" });
};
return () => (
<Modal
title="标题"
visible={visible.value}
onCancel={close}
onOk={() => {
message.success("操作成功");
close();
}}
>
// 内容
</Modal>
);
},
});
// 导出包装过的组件
export async function DynamicCellModal(props) {
return DynamicModal(CellModal, props);
}
// Edit.tsx
// 使用组件
import { DynamicCellModal } from "./CellModal";
DynamicCellModal({ props1: "", props2: {} }).then((data) => {
// data = { text: '这是子组件的数据' }
});
复制代码
这个时候有人注意到, 调用 DynamicCellModal 时, props 没有类型检查. 好的, 安排
此时 CellModal 的 props 包含三个属性, 分别是: onClose, props1, props2; 由于 onClose 是被我们动态强制注入的, 所以不需要调用方传入.
也就是 除了 onClose 之外的所有 props 都需要得到提示
这就好办了, 首先我们要先获取组件 CellModal 的 props 类型, 然后排除一下 onClose 即可
- 先声明类型:
1️⃣ // 获取组件的props类型
type GetPropType<T extends new (...args: any) => void> = {
[P in keyof InstanceType<T>["$props"]]: InstanceType<T>["$props"][P];
};
2️⃣ // 删除onClose类型
type DynamicPropType<T extends new (...args: any) => void> = Omit<
GetPropType<T>,
"onClose"
>;
复制代码
- 使用类型(修改 CellModal.tsx 文件)
// CellModal.tsx
export async function DynamicCellModal(
props: DynamicPropType<typeof CellModal>
) {
return DynamicModal(CellModal, props);
}
复制代码
到此, 整个改造完成.
大家有什么好的方式, 欢迎探讨!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END