基于 Arco Design UI 封装的 Modal 组件

在日常开发中,Modal 是一个使用频率非常高的 UI 组件。通过封装,我们可以让它更灵活、更便捷地与业务逻辑结合。基于 这篇文章 的启发,我编写了一个基于 Arco Design UI 的 Modal 组件封装。以下是封装的源码及其设计思路。

功能简介

我们封装了一个 modal 函数,能够:

  1. 动态传入内容组件和组件属性;
  2. 支持自定义配置(如标题、按钮文案等);
  3. 自动管理 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);
}

核心设计思路

  1. 动态创建容器

    我们通过 document.createElement('div') 创建了一个 DOM 容器,并在 document.body 中插入,以便动态渲染 Modal。

  2. 使用 createVNode 和 vueRender

    createVNode 用于生成内容组件的虚拟节点;

    vueRender 负责将 Modal 渲染到指定的容器中。

  3. 配置属性的动态更新

    将 ArcoModal 的所有配置存储在 metadata 中;

    通过 update 函数,可以动态更新配置,比如设置 confirmLoading 状态。

  4. 异步操作的处理

    如果 onOk 返回一个 Promise,则在弹窗确认按钮点击时,显示 loading 状态;

    确认完成后,自动关闭弹窗。

    使用方式

  5. 创建一个内容组件

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("取消操作");
  },
});
  1. 效果展示
  • 弹窗内容为 MyContentComponent;
  • 点击确认时,显示加载状态(2 秒后关闭弹窗);
  • 点击取消时,仅关闭弹窗。
    优化点和未来改进
  • 事件监听清理:弹窗关闭后,应清理挂载的 DOM 节点,防止内存泄漏。
    更加灵活的样式支持:可以通过额外的 class 或 style 配置,定制化样式。
    多实例支持:扩展当前实现,使其支持多个弹窗同时存在。
    通过以上封装,我们不仅提升了开发效率,也在代码结构上保持了高度的模块化和可读性。这种方法可以灵活地集成到实际业务中,希望对你有所帮助! 😊
相关推荐
雪芽蓝域zzs6 小时前
第4节:el-select 弹窗事件控制(打开 / 关闭、选中事件、弹窗滚动事件)
vue.js·elementui
PYB36 小时前
【Web·JS·基础】函数的使用和展运算符...objs
前端·javascript
默_笙6 小时前
🚥 给 RAG 装一台心电监护仪:LangSmith 全链路观测(上)
前端·javascript
变与不变8068 小时前
js同步和异步难点重点详解
开发语言·javascript·ecmascript
特立独行的猫a8 小时前
用仓颉写一个 Tauri:IPC 的每次往返实现原理(web层到仓颉层的触发过程)
前端·ui·harmonyos·tauri·鸿蒙·仓颉
Dovis(誓平步青云)8 小时前
多个链接不等于多份证据,新闻核验看板怎样合并来源
java·服务器·前端·javascript·人工智能·pdf·电脑
传奇开心果编程9 小时前
【现代声明式UI学与练】第1课 从命令式UI到声明式UI
学习·flutter·ui·swiftui·react·android jetpack
Dovis(誓平步青云)9 小时前
几个方案来回选不定?做一个随时切换的候选推荐页
android·java·服务器·开发语言·javascript·数据库·智能化
可乐鸡翅yeah_11 小时前
fmp4 碎片 MP4 格式 HLS 流,和传统 TS 分片有什么不一样
开发语言·javascript·ios·音视频·safari·m3u8·m3u8在线播放
ss27312 小时前
AI全栈实战 | 2.3-01 组件库实战:Element Plus 怎么搭出专业界面,过度封装为什么成维护负担
前端·javascript·elementui·vue