美文网首页深入解读JavaScript
如何优雅的使用ant.design的Modal组件?

如何优雅的使用ant.design的Modal组件?

作者: 悟C | 来源:发表于2021-10-28 20:32 被阅读0次

    一个数据列表少不了数据的增、删、改,一般我们都是通过Modal+Form实现表单数据的获取,在一个复杂的数据列表中我们需要好多个Modal+Form的配合,这时候我们需要维护好多个Modal的visible状态,例如下面的场景:

    const List = () => {
      const [visible1, setVisible1] = useState(false);
      const [visible2, setVisible2] = useState(false);
      const [visible3, setVisible3] = useState(false);
      const [visible4, setVisible4] = useState(false);
      const [visible5, setVisible5] = useState(false);
      const [visible6, setVisible6] = useState(false);
      return(
        <div>
          <Modal visible={visible1}><Form1/></Modal>
          <Modal visible={visible2}><Form2/></Modal>
          <Modal visible={visible3}><Form3/></Modal>
          <Modal visible={visible4}><Form4/></Modal>
          <Modal visible={visible5}><Form5/></Modal>
          <Modal visible={visible6}><Form6/></Modal>
        <div/>
      )
    }
    

    姑且不算其他代码就Modal和state我们就写了将近20行代码,那如何减少Modal和state的维护呢?

    封装useModal实现Modal优雅的使用:

    首先,我们看一下预期最简单的使用方式,我们只需要调用modal.open方法时,替换children就可达到Modal重复使用。

    export default () => {
      const [modal, ModalDOM] = useModal();
    
      const handleClick = () => {
        modal.open({
          title: '收集数据',
          children: <Form1/>,
          onOk: (values: any) => { // values为收集到的表单d
            modal.close();
          }
        });
      }
    
      return (
        <>
          <button onClick={handleClick}>自定义useModal</button>
          {ModalDOM}
        </>
      )
    }
    

    上面我们写的预期效果,我们知道useModal返回了一个钩子和一个Modal组件,通过调用钩子的open方法和close方法来控制Modal组件打开和关闭,下面我们通过预期,逆向实现主体函数:

    interface modalRefType {
      open: () => void;
      close: () => void;
      injectChildren: (child: React.ReactElement) => void;
      injectModalProps: (props: ModalProps) => void;
    }
    
    export default () => {
      const modalRef = useRef<modalRefType>();
      const handle = useMemo(() => {
        return {
          open: ({ children, ...rest }) => {
            modalRef.current.injectChildren(children);  // 注入子组件
            modalRef.current.injectModalProps(rest);    // 注入Modal的参数
            modalRef.current.open();
          },
          close: () => {
            modalRef.current.close();
          }
        };
      }, []);
    
      return [handle, <MyModal ref={modalRef} />] as const;
    }
    

    主体函数思路还是比较清晰,通过handle+useRef控制MyModal组件暴露出来的injectChildren、injectModalProps、open、close方法,下面我们再根据暴露出来的这些方法,逆向编程实现MyModal组件:

    const MyModal = memo(forwardRef((prop: any, ref) => {
      const [form] = Form.useForm();
      const [modalChildren, setModalChildren] = useState<React.ReactElement>(null);
      const [modalProps, setModalProps] = useState<ModalProps>({
        visible: false,
      });
    
      // ant.design 4.0 Form的onFinish触发回调
      const onFinish = useCallback((values: any) => {
        modalProps.onOk?.(values);
      }, [form, modalProps]);
    
      // 关闭当前Modal
      const onClose = useCallback(() => {
        setModalProps((source) => ({
          ...source,
          visible: false,
        }));
      }, [form]);
    
      // 关闭当前Modal
      const onOpen = useCallback(() => {
        setModalProps((source) => ({
          ...source,
          visible: true,
        }));
      }, [form]);
    
    
      useImperativeHandle(ref, () => ({
        // 注入Modal的子组件
        injectChildren: (element) => {
          setModalChildren(element);
        },
        // 注入Modal参数
        injectModalProps: (props) => {
          console.log(props)
          setModalProps((source) => {
            return {
              ...source,
              ...props,
            }
          });
        },
        // 打开Modal
        open: () => {
          onOpen();
        },
        // 关闭Modal
        close: () => {
          onClose();
        }
      }), []);
    
      // 这里的Modal是ant.design中的Modal
      return (
        <Modal
          {...modalProps}
          onCancel={onClose}
          onOk={() => form.submit()}. 
        >
          {
            modalChildren
            ?
            React.cloneElement(modalChildren, {
              onFinish,
              form,
              onClose,
            })
            : null
          }
        </Modal>
      )
    }));
    

    我们通过React.cloneElement克隆了子组件(就是我们的业务代码Form),并且注入form和onFinish实现对表单的控制,通过onOk方法的挟持,实现在点击Modal的ok按钮时获取表单数据。

    以上代码有一个细节,我们在改变状态的时候都是使用函数的方式setModalProps((sourcce) => ({...source, ...})),这主要是为了获取最新的状态进行。

    到目前为止,我们已经实现了useModal基础版本,它只能让我们创建表单,而无法编辑表单和脱离表单去使用。

    进阶实现useModal

    为了脱离Form使用和表单的编辑,我们添加了type、initialValues参数:

    ....  
    // 修改使用方式
      const handleClick = () => {
        modal.open({
          title: '收集数据',
          type: 'form',   // 类型为form时需要Form触发onFinish才触发onOk
          initialValues: { name: '原值' },  // 表单默认值
          children: <Form1/>,
          onOk: (values: any) => {
            console.log('收集到的表单', values);
            modal.close();
          }
        });
      }
      ....
    

    升级后useModal完整的代码:

    import React, { useRef, useMemo, memo, forwardRef, useCallback, useState, useImperativeHandle } from 'react';
    import { Modal, Form } from 'antd';
    import type { ModalProps } from 'antd';
    import "antd/dist/antd.css";
    
    const MyModal = memo(forwardRef((prop: any, ref) => {
      const [form] = Form.useForm();
      const [modalChildren, setModalChildren] = useState<React.ReactElement>(null);
      const [modalProps, setModalProps] = useState<ModalProps>({
        visible: false,
      });
      const typeRef = useRef<string>();
    
      // ant.design 4.0 Form的onFinish触发回调
      const onFinish = useCallback((values: any) => {
        modalProps.onOk?.(values);
      }, [form, modalProps]);
    
      // 关闭当前Modal
      const onClose = useCallback(() => {
        setModalProps((source) => ({
          ...source,
          visible: false,
        }));
      }, [form]);
    
      // 关闭当前Modal
      const onOpen = useCallback(() => {
        setModalProps((source) => ({
          ...source,
          visible: true,
        }));
      }, [form]);
    
    
      useImperativeHandle(ref, () => ({
        // 注入Modal的子组件
        injectChildren: (element) => {
          setModalChildren(element);
        },
        // 注入Modal参数
        injectModalProps: (props) => {
          console.log(props)
          setModalProps((source) => {
            return {
              ...source,
              ...props,
            }
          });
        },
        // 打开Modal
        open: () => {
          onOpen();
        },
        // 关闭Modal
        close: () => {
          onClose();
        },
        // 设置表单数据
        setFieldsValue: (values: any) => {
          form.setFieldsValue?.(values);
        },
        setType: (type: string) => {
          typeRef.current = type;
        }
      }), []);
    
      const handleOk = useCallback(() => {
        if (typeRef.current === 'form') {
          form.submit();
        } else {
          modalProps.onOk?.(null);
        }
      }, [form, modalProps]);
    
      // 这里的Modal是ant.design中的Modal
      return (
        <Modal
          {...modalProps}
          onCancel={onClose}
          onOk={handleOk}
        >
          {
            modalChildren
            ?
            React.cloneElement(modalChildren, {
              onFinish,
              form,
              onClose,
            })
            : null
          }
        </Modal>
      )
    }));
    
    interface modalRefType {
      open: () => void;
      close: () => void;
      injectChildren: (child: React.ReactElement) => void;
      injectModalProps: (props: ModalProps) => void;
      setFieldsValue: (values: any) => void;
      setType: (type: string) => void;
    }
    
    interface openArgType extends ModalProps {
      children?: React.ReactElement,
      type?: 'form' | 'default',
      initialValues?: {
        [key: string]: any;
      },
    }
    
    export default () => {
      const modalRef = useRef<modalRefType>();
      const handle = useMemo(() => {
        return {
          open: ({ children, type, initialValues, ...rest }: openArgType) => {
            modalRef.current.setType(type);
            modalRef.current.injectChildren(children);  // 注入子组件
            modalRef.current.injectModalProps(rest);    // 注入Modal的参数
            modalRef.current.open();
    
            if (initialValues && type === 'form') {
              modalRef.current.setFieldsValue?.(initialValues);
            }
          },
          close: () => {
            modalRef.current.close();
          }
        };
      }, []);
    
      return [handle, <MyModal ref={modalRef} />] as const;
    }
    

    完结:以上我们通过了逆向的编程方式,实现了我们需求的useModal钩子,用这样的一个hook使用Modal是不是优雅多了。

    相关文章

      网友评论

        本文标题:如何优雅的使用ant.design的Modal组件?

        本文链接:https://www.haomeiwen.com/subject/uctgvltx.html