在日常开发中,Modal 是一个使用频率非常高的 UI 组件。通过封装,我们可以让它更灵活、更便捷地与业务逻辑结合。基于 这篇文章 的启发,我编写了一个基于 Arco Design UI 的 Modal 组件封装。以下是封装的源码及其设计思路。
功能简介
我们封装了一个 modal 函数,能够:
- 动态传入内容组件和组件属性;
- 支持自定义配置(如标题、按钮文案等);
- 自动管理 Modal 的打开、关闭,以及异步状态(如 confirmLoading)。
通过封装,开发者只需调用一个函数,即可快速创建一个带有逻辑控制的弹窗,避免重复性代码。
源码实现
以下是完整源码,包含核心逻辑和注释。
js
// modal.ts
import {
createVNode,
render as vueRender,
DefineComponent,
PropType,
} from "vue";
import { Modal as ArcoModal, ModalConfig } from "@arco-design/web-vue";
// 扩展 ModalConfig 类型,添加 confirmLoading 属性
interface ExtendedModalConfig extends ModalConfig {
confirmLoading?: boolean;
}
/**
* 创建一个动态 Modal 的封装
* @param {DefineComponent} content - 要显示在 Modal 中的内容组件
* @param {Record<string, any>} props - 传递给内容组件的属性
* @param {Omit<ExtendedModalConfig, 'visible'>} config - Modal 的配置选项,排除 'visible'
*/
export default function modal<T extends DefineComponent>(
content: T,
props: InstanceType<T>["$props"],
config: Omit<ExtendedModalConfig, "visible">
) {
const container = document.createElement("div");
const _contentVnode = createVNode(content, props);
const metadata = Object.create({
okText: "确定",
cancelText: "取消",
visible: true,
...config,
});
// 关闭弹窗逻辑
function close() {
metadata.visible = false;
update();
setTimeout(() => vueRender(null, container), 300);
}
// 更新弹窗配置
function update(newConfig?: Partial<ExtendedModalConfig>) {
Object.assign(metadata, newConfig);
vueRender(
createVNode(ArcoModal, metadata, {
default: () => _contentVnode,
}),
container
);
}
metadata.onCancel = async function (...arg: any[]) {
await config.onCancel?.(...arg);
close();
};
metadata.onOk = async function () {
if (typeof config.onOk !== "function") {
close();
return;
}
const result = config.onOk() as Promise<any> | unknown;
if (!(result instanceof Promise)) {
close();
return;
}
update({ confirmLoading: true });
return result
.then(() => {
update({ confirmLoading: false });
close();
})
.catch(() => {
update({ confirmLoading: false });
});
};
// 初次渲染
update();
document.body.appendChild(container);
}
核心设计思路
-
动态创建容器
我们通过 document.createElement('div') 创建了一个 DOM 容器,并在 document.body 中插入,以便动态渲染 Modal。
-
使用 createVNode 和 vueRender
createVNode 用于生成内容组件的虚拟节点;
vueRender 负责将 Modal 渲染到指定的容器中。
-
配置属性的动态更新
将 ArcoModal 的所有配置存储在 metadata 中;
通过 update 函数,可以动态更新配置,比如设置 confirmLoading 状态。
-
异步操作的处理
如果 onOk 返回一个 Promise,则在弹窗确认按钮点击时,显示 loading 状态;
确认完成后,自动关闭弹窗。
使用方式
-
创建一个内容组件
js
// MyContentComponent.ts
import { defineComponent } from "vue";
export default defineComponent({
name: "MyContentComponent",
props: {
title: String,
},
setup(props) {
return () => <div>标题:{props.title}</div>;
},
});
7. 调用封装的 modal 函数
import modal from "./modal";
import MyContentComponent from "./MyContentComponent";
modal(MyContentComponent, { title: "动态弹窗" }, {
title: "这是一个标题",
onOk: () => {
return new Promise((resolve) => {
setTimeout(() => {
console.log("确认操作完成");
resolve(true);
}, 2000);
});
},
onCancel: () => {
console.log("取消操作");
},
});
- 效果展示
- 弹窗内容为 MyContentComponent;
- 点击确认时,显示加载状态(2 秒后关闭弹窗);
- 点击取消时,仅关闭弹窗。
优化点和未来改进 - 事件监听清理:弹窗关闭后,应清理挂载的 DOM 节点,防止内存泄漏。
更加灵活的样式支持:可以通过额外的 class 或 style 配置,定制化样式。
多实例支持:扩展当前实现,使其支持多个弹窗同时存在。
通过以上封装,我们不仅提升了开发效率,也在代码结构上保持了高度的模块化和可读性。这种方法可以灵活地集成到实际业务中,希望对你有所帮助! 😊