标题又名:简单弹窗、多弹窗、复杂弹窗怎么做代码和状态的解耦。

关键字: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>
    ) 
}
​

非常的痛:

  1. 如果弹窗在处理完内部流程后,又还有返回值,这有又会有一大通的处理函数。
  2. 如果这些代码都写在一个文件中还是不太容易维护的。

而且随着业务的断增长,每次这么体验很差。

期望的使用方式

因此有没有一种更贴近业务实际的方案。

让弹窗只专注于自己的内部事务,同时又能将控制权交给调用方呢。

或者说我就是不喜欢 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>
    ) 
}

像这样分离两者的代码,这样处理起来肯定是清爽很多的。

实现思路

实现的思路都是一致的。

  1. 创建占位符组件,放到应用的某处。
  2. 将相关状态集管理,并与Modal做好关联。
  3. 暴露控制权。

因此基于不同的状态管理方案,实现是多种多样的。相信很多大佬都自己内部封装过不少了。

但是在此基础上,我还想要3点。

  1. API使用简单,但也允许一定配置。
  2. 返回值类型推导,除了帮我管理 close 和 open 还要让我用起来带提示的。
  3. 无入侵性,不依赖框架以外的东西。

ez-modal-react

emm......苦于市面上找不到这类产品(感觉同类并不多……讨论的也不多?)

于是我自己开源了一个。回应上文实现思路,可以点击看代码,几百行而已。

并不是突发奇想而来,其实相关特性早就在企业内部是使用多年了。我也是受前辈和社区启发。

基本特性

  1. 基于Promise封装
  2. 返回值类型推导
  3. 没有入侵性,体积小。

使用画面

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

觉得有用帮我点个星\~\~\~ 感恩,有啥问题可以提出,看到会回复。如果有需要会持续维护该项目。


raotao
1 声望0 粉丝