[React]基于Antd的FormModal的组件封装以及useFormModal的hooks封装

[React]基于Antd的FormModal的组件封装以及useFormModal的hooks封装

场景

很常见,打开弹窗输入表单等...

封装后,弹窗自行挂载到body上,只需关注表达逻辑和打开关闭逻辑,其它的已经帮你管理好了

源码

ts 复制代码
import React, { useRef, useMemo, memo, forwardRef, useCallback, useState, useImperativeHandle, useEffect } from 'react';
import { Modal, Form } from 'antd';
import type { ModalProps } from 'antd';
import { createPortal, render, unmountComponentAtNode } from 'react-dom';

export const MyModal = memo(forwardRef((props: any, ref) => {
  useEffect(() => {
    console.log('modal had mounted')
  }, [])
  const [form] = Form.useForm();
  const [modalChildren, setModalChildren] = useState<React.ReactElement | null>(null);
  const [modalProps, setModalProps] = useState<ModalProps>({
    visible: false,
    ...(props ?? {})
  });
  const typeRef = useRef<string>();

  const onFinish = useCallback((values: any) => {
    modalProps.onOk?.(values);
  }, [form, modalProps]);

  const onClose = useCallback(() => {
    if (typeRef.current === 'form') {
      form.resetFields();
    }
    setModalProps((source) => ({
      ...source,
      visible: false,
    }));
  }, [form]);

  const onOpen = useCallback(() => {
    setModalProps((source) => ({
      ...source,
      visible: true,
    }));
  }, [form]);

  useImperativeHandle(ref, () => ({
    injectChildren: (element) => {
      setModalChildren(element);
    },
    injectModalProps: (props) => {
      console.log(props)
      setModalProps((source) => {
        return {
          ...source,
          ...props,
        }
      });
    },
    open: () => {
      onOpen();
    },
    close: () => {
      onClose();
    },
    setFieldsValue: (values: any) => {
      form.setFieldsValue?.(values);
    },
    setType: (type: string) => {
      typeRef.current = type;
    }
  }), []);

  const handleOk = useCallback((e: any) => {
    if (typeRef.current === 'form') {
      form.submit();
    } else {
      modalProps.onOk?.(e);
    }
  }, [form, modalProps]);

  return (
    <Modal
      {...modalProps}
      onCancel={onClose}
      onOk={handleOk}
    >
      {
        modalChildren
          ? React.cloneElement(modalChildren, typeRef.current === 'form'
            ? {
                onFinish,
                form,
                onClose,
              }
            : { 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;
  },
}

const useMyModal = () => {
  const modalRef = useRef<modalRefType>();
  const handle = useMemo(() => {
    return {
      open: ({ children, type, initialValues, ...rest }: openArgType) => {
        console.log('modalRef.current: ', modalRef.current);
        modalRef.current?.setType(type ?? '');
        modalRef.current?.injectChildren(children ?? <div>111</div>);
        modalRef.current?.injectModalProps(rest);
        modalRef.current?.open();
        if (initialValues && type === 'form') {
          modalRef.current?.setFieldsValue?.(initialValues);
        }
      },
      close: () => {
        modalRef.current?.close();
      }
    };
  }, []);

  const containerRef = useRef<any>(document.createDocumentFragment())
  useEffect(() => {
    render(createPortal(<MyModal key="my-modal" ref={modalRef} />, document.body) as any, containerRef.current)

    return () => {
      unmountComponentAtNode(containerRef.current)
    }
  }, [])

  return [handle] as const;
}

export default useMyModal

使用Demo

ts 复制代码
const [modalHandler] = useMyModal()

// 表单组件内容
const AvailabilityForm = (props) => {
  return (
    <Form
      name="availability_form"
      {
      ...props
      }
    >
      <Form.Item
        name="time"
        rules={REQUIRED_RULE}
      >
        <DatePicker.RangePicker className='w-full' />
      </Form.Item>
    </Form>
  )
}

// 点击发布  
const handlePublish = useMemoizedFn(async () => {
    modalHandler.open({
      title: 'Available Date',
      type: 'form',
      initialValues: {},
      children: <AvailabilityForm></AvailabilityForm>,
      onOk: async (values: any) => {
        console.log('form vals', values);
        const timeRes: any[] = []

        console.log(Object.entries(values))
        Object.entries(values).forEach((item: any) => {
          const timeVal: any[] = item[1]
          if (Array.isArray(timeVal) && timeVal.length === 2) {
            timeRes.push({
              startTime: dayjs(timeVal[0]).utc().valueOf(),
              endTime: dayjs(timeVal[1]).utc().valueOf(),
            })
          }
        })

        try {
          const res = await netPublishListing(listingId, {
            availability: timeRes
          })

          if (res) {
            message.success('Success to publish')
            fetchItem()
            modalHandler.close();
          }
          console.log(res)
        } catch (err) {
          console.log('err: ', err);
        }
      },
      destroyOnClose: true,
      maskClosable: false
    });
  })
相关推荐
elangyipi1236 分钟前
前端面试题:如何减少页面重绘跟重排
前端·面试·html
一棵开花的树,枝芽无限靠近你12 分钟前
【face-api.js】2️⃣ NetInput - 神经网络输入封装类
开发语言·javascript·神经网络
想学后端的前端工程师13 分钟前
【前端安全防护实战指南:从XSS到CSRF全面防御】
前端·安全·xss
TAEHENGV17 分钟前
关于应用模块 Cordova 与 OpenHarmony 混合开发实战
android·javascript·数据库
资深低代码开发平台专家17 分钟前
MicroQuickJS:为极致资源而生的嵌入式JavaScript革命
开发语言·javascript·ecmascript
czlczl2002092517 分钟前
基于 Spring Boot 权限管理 RBAC 模型
前端·javascript·spring boot
未来之窗软件服务18 分钟前
幽冥大陆(六十七) PHP5.x SSL 文字加密—东方仙盟古法结界
服务器·前端·ssl·仙盟创梦ide·东方仙盟
nnsix23 分钟前
【C#】HttpPost请求 - Query参数 - URL编码方法
java·javascript·c#
小北方城市网29 分钟前
第 10 课:Node.js 后端企业级进阶 —— 任务管理系统后端优化与功能增强(续)
大数据·前端·vue.js·ai·性能优化·node.js
华仔啊30 分钟前
JavaScript 有哪些数据类型?它们在内存里是怎么存的?
前端·javascript