[React]基于Antd的FormModal的组件封装以及useFormModal的hooks封装
[React]基于Antd的FormModal的组件封装以及useFormModal的hooks封装
场景
很常见,打开弹窗输入表单等…
封装后,弹窗自行挂载到body上,只需关注表达逻辑和打开关闭逻辑,其它的已经帮你管理好了
源码
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
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
});
})