如何優(yōu)雅的使用ant.design的Modal組件?

一個(gè)數(shù)據(jù)列表少不了數(shù)據(jù)的增、刪、改,一般我們都是通過(guò)Modal+Form實(shí)現(xiàn)表單數(shù)據(jù)的獲取,在一個(gè)復(fù)雜的數(shù)據(jù)列表中我們需要好多個(gè)Modal+Form的配合,這時(shí)候我們需要維護(hù)好多個(gè)Modal的visible狀態(tài),例如下面的場(chǎng)景:

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行代碼,那如何減少M(fèi)odal和state的維護(hù)呢?

封裝useModal實(shí)現(xiàn)Modal優(yōu)雅的使用:

首先,我們看一下預(yù)期最簡(jiǎn)單的使用方式,我們只需要調(diào)用modal.open方法時(shí),替換children就可達(dá)到Modal重復(fù)使用。

export default () => {
  const [modal, ModalDOM] = useModal();

  const handleClick = () => {
    modal.open({
      title: '收集數(shù)據(jù)',
      children: <Form1/>,
      onOk: (values: any) => { // values為收集到的表單d
        modal.close();
      }
    });
  }

  return (
    <>
      <button onClick={handleClick}>自定義useModal</button>
      {ModalDOM}
    </>
  )
}

上面我們寫的預(yù)期效果,我們知道useModal返回了一個(gè)鉤子和一個(gè)Modal組件,通過(guò)調(diào)用鉤子的open方法和close方法來(lái)控制Modal組件打開(kāi)和關(guān)閉,下面我們通過(guò)預(yù)期,逆向?qū)崿F(xiàn)主體函數(shù):

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的參數(shù)
        modalRef.current.open();
      },
      close: () => {
        modalRef.current.close();
      }
    };
  }, []);

  return [handle, <MyModal ref={modalRef} />] as const;
}

主體函數(shù)思路還是比較清晰,通過(guò)handle+useRef控制MyModal組件暴露出來(lái)的injectChildren、injectModalProps、open、close方法,下面我們?cè)俑鶕?jù)暴露出來(lái)的這些方法,逆向編程實(shí)現(xiàn)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觸發(fā)回調(diào)
  const onFinish = useCallback((values: any) => {
    modalProps.onOk?.(values);
  }, [form, modalProps]);

  // 關(guān)閉當(dāng)前Modal
  const onClose = useCallback(() => {
    setModalProps((source) => ({
      ...source,
      visible: false,
    }));
  }, [form]);

  // 關(guān)閉當(dāng)前Modal
  const onOpen = useCallback(() => {
    setModalProps((source) => ({
      ...source,
      visible: true,
    }));
  }, [form]);


  useImperativeHandle(ref, () => ({
    // 注入Modal的子組件
    injectChildren: (element) => {
      setModalChildren(element);
    },
    // 注入Modal參數(shù)
    injectModalProps: (props) => {
      console.log(props)
      setModalProps((source) => {
        return {
          ...source,
          ...props,
        }
      });
    },
    // 打開(kāi)Modal
    open: () => {
      onOpen();
    },
    // 關(guān)閉Modal
    close: () => {
      onClose();
    }
  }), []);

  // 這里的Modal是ant.design中的Modal
  return (
    <Modal
      {...modalProps}
      onCancel={onClose}
      onOk={() => form.submit()}. 
    >
      {
        modalChildren
        ?
        React.cloneElement(modalChildren, {
          onFinish,
          form,
          onClose,
        })
        : null
      }
    </Modal>
  )
}));

我們通過(guò)React.cloneElement克隆了子組件(就是我們的業(yè)務(wù)代碼Form),并且注入form和onFinish實(shí)現(xiàn)對(duì)表單的控制,通過(guò)onOk方法的挾持,實(shí)現(xiàn)在點(diǎn)擊Modal的ok按鈕時(shí)獲取表單數(shù)據(jù)。

以上代碼有一個(gè)細(xì)節(jié),我們?cè)诟淖儬顟B(tài)的時(shí)候都是使用函數(shù)的方式setModalProps((sourcce) => ({...source, ...})),這主要是為了獲取最新的狀態(tài)進(jìn)行。

到目前為止,我們已經(jīng)實(shí)現(xiàn)了useModal基礎(chǔ)版本,它只能讓我們創(chuàng)建表單,而無(wú)法編輯表單和脫離表單去使用。

進(jìn)階實(shí)現(xiàn)useModal

為了脫離Form使用和表單的編輯,我們添加了type、initialValues參數(shù):

....  
// 修改使用方式
  const handleClick = () => {
    modal.open({
      title: '收集數(shù)據(jù)',
      type: 'form',   // 類型為form時(shí)需要Form觸發(fā)onFinish才觸發(fā)onOk
      initialValues: { name: '原值' },  // 表單默認(rèn)值
      children: <Form1/>,
      onOk: (values: any) => {
        console.log('收集到的表單', values);
        modal.close();
      }
    });
  }
  ....

升級(jí)后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觸發(fā)回調(diào)
  const onFinish = useCallback((values: any) => {
    modalProps.onOk?.(values);
  }, [form, modalProps]);

  // 關(guān)閉當(dāng)前Modal
  const onClose = useCallback(() => {
    setModalProps((source) => ({
      ...source,
      visible: false,
    }));
  }, [form]);

  // 關(guān)閉當(dāng)前Modal
  const onOpen = useCallback(() => {
    setModalProps((source) => ({
      ...source,
      visible: true,
    }));
  }, [form]);


  useImperativeHandle(ref, () => ({
    // 注入Modal的子組件
    injectChildren: (element) => {
      setModalChildren(element);
    },
    // 注入Modal參數(shù)
    injectModalProps: (props) => {
      console.log(props)
      setModalProps((source) => {
        return {
          ...source,
          ...props,
        }
      });
    },
    // 打開(kāi)Modal
    open: () => {
      onOpen();
    },
    // 關(guān)閉Modal
    close: () => {
      onClose();
    },
    // 設(shè)置表單數(shù)據(jù)
    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的參數(shù)
        modalRef.current.open();

        if (initialValues && type === 'form') {
          modalRef.current.setFieldsValue?.(initialValues);
        }
      },
      close: () => {
        modalRef.current.close();
      }
    };
  }, []);

  return [handle, <MyModal ref={modalRef} />] as const;
}

完結(jié):以上我們通過(guò)了逆向的編程方式,實(shí)現(xiàn)了我們需求的useModal鉤子,用這樣的一個(gè)hook使用Modal是不是優(yōu)雅多了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容