概述
在最近使用 MUI 的过程中,我突然发现 MUI 的 Dialog 好像没有如同 Arco Design 那样自带命令式调用方法,这个缺失的特性导致我在使用 Dialog 时(即便我只是想很简单地显示一点提示信息),必须显式地在 UI 中声明一个 Dialog 组件。
编写重复代码的过程其实是非常痛苦的,而当重复的代码慢慢累积起来之后,核心代码的可读性也会受到一定的影响。
为了减少编写重复代码的时间,我想使用”池化“的思维来构建一个全局的临时 Dialog 组,同时提供一个可以在全局进行调用的 Hook,把展示临时 Dialog 的声明缩略到一条语句以内。
外部依赖
- React* MUI* Recoil(可选,不想用状态管理库的话,本文也提供了一种通过手写 Observable 同步全局状态的办法)仓库
==
有人可能更喜欢从源码入手去理解逻辑,这里先把 Demo 的仓库丢出来:
GitHub - sheason2019/global-dialog-demo
起步
为了节省时间,一些比较基础的部分我就不讲的太细了。
在开始正式编码之前,我们首先需要使用Vite搭建起一个基础的 React-Typescript 框架,然后将 React、MUI和Recoil这三个依赖库添加至项目。
方便起见,这里把除了 React 以外的安装页面链接都附在了上面的超链接文本里。
分析需求
在上面的概述中,我提到了我希望在使用 MUI 时能像使用 Arco Design 一样通过命令式的方式调用 Dialog,从而提升开发体验和代码结构。
根据这个核心的需要我们可以很快推导出我们需要做的事情:
graph TD;A[命令式调用Dialog] --> B[需要提供可以全局调用的Controller];A --> G[需要提供基础的Dialog模板显示内容]G --> H[使用MUI设计View]A --> C[需要提供可以渲染Dialog的Model]B --> E[使用Hook提供API Controller]C --> F[根据需要设计Model]
可以看到,这是一个很典型的 MVC 设计,所以,我们接下来按照最常规的 MVC 开发流程来实现这个需求即可。
Model 层的设计
在通常的 Web 程序设计中,使用 Dialog 来展示提示信息的场景多种多样,具体可以参考Arco Design - Dialog 消息展示,我们这里只实现两种最基本的 Dialog 场景:信息展示和操作确认。
首先让我们来思考一下一个最基本的 Dialog 需要声明哪些信息:
interface IGlobalDialog {// 因为采用循环渲染的方式渲染Dialog组,所以需要一个UUID来确保Dialog的Key不重复uuid: string;title: React.ReactNode;content: React.ReactNode;
}
显然,对于一个最基础的 Dialog,我们最核心的诉求是让它能展示我们需要的标题和内容。
以IGlobalDialog
为基础类型,我们可以很容易地扩展出信息展示 Dialog 和操作确认 Dialog 的接口:
// 信息展示Dialog
interface IGlobalNormalDialog extends IGlobalDialog {type: "normal";
}
// 操作确认Dialog
interface IGlobalConfirmDialog extends IGlobalDialog {type: "confirm";
}
然后,我们可以将这两个接口组合起来:
type GlobalDialogTypeCompose =| IGlobalConfirmDialogState| IGlobalNormalDialogState;
现在,我们就得到了一个可以用来表示所有可用 Dialog 接口的接口:GlobalDialogTypeCompose
。
通过这个接口,我们可以基于 Recoil 创建出一个全局状态用来管理这些 Dialog:
const globalAlertDialogState = atom<GlobalDialogTypeCompose[]>({key: "common/global-alert-dialog",default: [],
});
到了这一步,Model 层的建设其实就已经完成的差不多了,在 React 组件内部编写相应的循环渲染逻辑后,我们就可以根据globalAlertDialogState
中存储的数据启动和关闭自己需要的 Dialog。
但是!事情并没有这么简单。 在关闭 Dialog 时,如果你的操作只是简单地从globalAlertDialogState
中移除掉对应的结构体,