在React中优雅地使用弹窗——useModal

10,892 阅读2分钟

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

前言

上一篇文章中介绍了以HOC的方法封装一个withModalapi,将代码量压缩了一半。但是我们再思考一下这段代码:

export default function TestPage() {
  const [visiable, setVisiable] = useState(false);
  // 打开弹窗
  const open = () => {
    setVisiable(true);
  };
  //关闭弹窗
  const close = () => {
    setVisiable(false);
  };
  //点击确定提交表单
  const submit = (ref: MutableRefObject<FormInstance>) => {
    ref.current.submit();
  };
  const afterSubmit = () => {
    close();
  };
  const UserFormModal = withModal({ title: '新建用户' }, { afterSubmit })(React.forwardRef(UserForm));

  return (
    <div>
      <div className="text-center">
        <Button type="primary" onClick={open}>
          新建
        </Button>
      </div>
      <UserFormModal visible={visiable} onCancel={close} onOk={submit} />
    </div>
  );
}

TestPage作为一个Page组件,需要关心Modal的关闭吗?

关注点分离

对于这个表单弹窗,TestPage关心的是open,至于取消、提交、请求,这些操作的发生点都在组件内,因此也不应该由TestPage关心。 我们希望的调用方式是:

const {open,Modal} = useModal(...)
...
<Button type="primary" onClick={open}>新建</Button>

useModal

思路

控制弹窗显示的是Modal的visiable属性,如果利用hooks的useStateapi控制visiable,那么我们只要将setVisiable暴露出去便是外层想要的open方法了。

另外这里的弹窗,需要和表单进行交互,这并不是通用弹窗的必须逻辑,因此不如直接将这个Modal定位为FormModal,不再背负通用Modal的设计负担,这样无论是语义还是使用方式,都会简单很多。

useFormModal

import type { ModalProps } from 'antd';
import { Modal } from 'antd';
import type { FormInstance } from 'antd/es/form';
import React, { useState } from 'react';

const useFormModal = (modalProps: ModalProps, Slot: React.FC<any>) => {
  const [visiable, setVisiable] = useState(false);
  const open = () => {
    setVisiable(true);
  };
  const close = () => {
    setVisiable(false);
  };
  const FormModal = (slotProps: any) => {
    const onCancel = () => {
      close();
    };

    const ref = React.useRef<FormInstance>();
    const ok = () => {
      ref.current?.submit()
    };
    return (
      <Modal
        onCancel={onCancel}
        onOk={ok}
        visible={visiable}
        wrapClassName="modal-wrap"
        okText="提交"
        cancelButtonProps={{ shape: 'round' }}
        okButtonProps={{ shape: 'round' }}
        width={600}
        {...modalProps}
      >
        <Slot ref={ref} {...slotProps} afterSubmit={close} />
      </Modal>
    );
  };

  return {
    FormModal,
    open,
  };
};
export default useFormModal;

可以看到FormModal内部处理了表单提交和弹窗关闭的逻辑,同时还支持使用者传入自定义的Modal和Form组件的props,做到了高内聚和可扩展。

使用效果

export default function TestPage() {


  const {open,FormModal: UserModal} = useFormModal({title:'新建用户'},React.forwardRef(UserForm))

  return (
    <div>
      <div className="text-center">
        <Button type="primary" onClick={open}>
          新建
        </Button>
      </div>
      <UserModal />
    </div>
  );
}

至此,我们成功地将一个70行左右的代码,压缩到15行以内。而干净的调用方式,给我们带来的,不仅是本次迭代工作量的减负,代码越少,可读性和可维护性越高,即便后续有多次迭代和需求变更,也可以尽可能地通过小范围改动安全地完成。