标题又名:简单弹窗、多弹窗、复杂弹窗怎么做代码和状态的解耦。
关键字:react / modal
问题
实际业务中,不乏弹窗组件中包含大量复杂的业务逻辑。如:
function Order() {
// 省略上百行方法状态
const [visible,setVisible] = useState(false)
const withModalState = useState<any>()
return (
<Modal>
<Input/>
<Input/>
<Select/>
<Checkbox/>
</Modal>
)
}
甚至还有多弹窗的情形。如:
function Order() {
// 省略上百行方法状态
const [visible1,setVisible1] = useState(false)
const [visible2,setVisible2] = useState(false)
const [visible3,setVisible3] = useState(false)
const withModalState1 = useState<any>()
const withModalState2 = useState<any>()
const withModalState3 = useState<any>()
// 省略 不懂多少 handlexx
return (
<main>
...
<Modal1></* 省略很多代码 */></Modal1>
<Modal2></* 省略很多代码 */></Modal3>
<Modal3></* 省略很多代码 */></Modal3>
</main>
)
}
非常的痛:
- 如果弹窗在处理完内部流程后,又还有返回值,这有又会有一大通的处理函数。
- 如果这些代码都写在一个文件中还是不太容易维护的。
而且随着业务的断增长,每次这么体验很差。
期望的使用方式
因此有没有一种更贴近业务实际的方案。
让弹窗只专注于自己的内部事务,同时又能将控制权交给调用方呢。
或者说我就是不喜欢 Modal 代码堆在一起……
如下:
// Modal1.tsx
export function Modal1(props) return <Modal></* ... */></Modal>
// 甚至可以将MOdal中的逻辑做的更聚合。通过2次封再导出给各方使用
export const function Check_XXX_Window() {/* */ return open(Modal1)}
export const function Check_XXX_By_Id_Window() { return open(Modal1)}
export const function Check_XXX_Of_Ohter_Window() { return open(Modal1)}
// Order.tsx
function Order() {
function xxHndanle {
const expect = Check_XXX_Window(Modal1,args) // 调用方法,传入参数,获得预期值 ,完成1个流程
}
return (
<main>
...
// 不实际挂载 Modal 组件
</main>
)
}
像这样分离两者的代码,这样处理起来肯定是清爽很多的。
实现思路
实现的思路都是一致的。
- 创建占位符组件,放到应用的某处。
- 将相关状态集管理,并与Modal做好关联。
- 暴露控制权。
因此基于不同的状态管理方案,实现是多种多样的。相信很多大佬都自己内部封装过不少了。
但是在此基础上,我还想要3点。
- API使用简单,但也允许一定配置。
- 返回值类型推导,除了帮我管理 close 和 open 还要让我用起来带提示的。
- 无入侵性,不依赖框架以外的东西。
ez-modal-react
emm......苦于市面上找不到这类产品(感觉同类并不多……讨论的也不多?)
于是我自己开源了一个。回应上文实现思路,可以点击看代码,几百行而已。
并不是突发奇想而来,其实相关特性早就在企业内部是使用多年了。我也是受前辈和社区启发。
基本特性
- 基于Promise封装
- 返回值类型推导
- 没有入侵性,体积小。
使用画面
import EasyModal, { InnerModalProps } from 'ez-modal-react';
+ interface IProps extends InnerModalProps<'fybe?'> /*传入返回值类型*/ {
+ age: number;
+ name: string;
+ }
export const InfoModal = EasyModal.create(
+ (props: Props) => {
return (
<Modal
title="Hello"
open={props.visible}
onOk={() => {
+ props.hide(); // warn 应有 1 个参数,但获得 0 个。 (property) hide: (result: "fybe?") => void ts(2554)
}}
onCancel={() => {
props.hide(null); //safe hide 接受 null 作为参数。它兼具 hide resolve 两种功能。
}}
>
<h1>{props.age}</h1>
</Modal>
);
});
+ // warn 类型 "{ name: string; }" 中缺少属性 "age",但类型 "ModalProps<Props, "fybe?">" 中需要该属性。
EasyModal.show(InfoModal, { name: 'foo' }).then((resolve) => {
console.log(resolve);
+ //输出 "fybe?"
});
也支持用 hook
import EasyModal, { useModal, InnerModalProps } from 'ez-modal-react';
interface IProps extends InnerModalProps<'苏振华'>/* 指定返回值类型 */ {
age: number;
name: string;
}
export const Info = EasyModal.create((props: Props) => {
const modal = useModal<Props>();
function handleOk(){
modal.hide(); // ts(2554) (property) hide: (result: "苏振华") => void ts(2554)
}
return <Modal open={modal.visible} onOk={handleOk} onCancel={modal.hide}></Modal>
});
EasyModal.show(Info,{age:18,}) // 缺少属性 "age"
还有一些特性如支持配置 hide 弹窗时的默认行为。(我认为大多数情况下可能用不上)
export const Info = EasyModal.create((props: Props) => {
const modal = useModal<Props>();
function handleOk(){
modal.hide();
+ modal.resolve('苏振华') // 需要手动抛出成功
+ modal.remove() // 需要手动注销组件。可用于反复打开弹窗,但是不希望状态被清除的场景。
}
return <Modal open={modal.visible} onOk={handleOk} onCancel={modal.hide}></Modal>
// Ohter.tsx
EasyModal.open(Info, {},
+ config:{
+ resolveOnHide:false, // 默认为 true
+ removeOnHide:false, // 默认为 true
+ }
);
当然以上是针对弹窗内有复杂业务场景的状况。
大部分场景都是调用方只在乎 open或close ,仅仅解耦代码这一项好处,就可以让代码变得清爽。
如常用的有展示类,设置类的弹窗,TS类型都用不上。
仓库
其他就不一一介绍了,主要的已经说完了,想了解更多,可以看看仓库。github
🎮 Codesandbox Demo
Codesandbox是一个线上集成环境,可以直接打开玩玩。点击 Demo Link 前往
初衷
让弹窗使用更轻松。
包名 ez 开头,是因为我DOTA在东南亚打多了,觉得该词特别贴切。
授之于鱼叉
GitHub仓库地址
:ez-modal-react
觉得有用帮我点个星\~\~\~ 感恩,有啥问题可以提出,看到会回复。如果有需要会持续维护该项目。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。