antd官网提供了弹窗的功能,同时也提供了一些简洁的确认框询问用户,如Modal.conform,Modal.info等,但是这些简洁的弹窗并不能满足复杂的form表单场景,如果使用普通的对话框,每次需要一个弹窗的时候,都要显式的设置如下一堆模版代码:
const App: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
在做内部项目中,我们经常需要使用弹窗来添加,删除,更新一些内容,甚至同一个页面需要多个弹窗的情况,每次调用弹窗的时候,都写一堆模版代码显然不够优雅,怎么能够每次通过类似Modal.confirm的方式弹出弹窗,点击关闭的时候关闭弹窗呢,而不需要写一堆模版代码的?
目标是将visible,onOk, onCancel等操作封装在子组件中,父组件只需要通过modal.open的方式调用即可。通过react hooks实现一个useModal的方式实现。 具体代码实现:
import React, { forwardRef, memo, useCallback, useImperativeHandle, useState, useRef, useMemo } from 'react';
import {
Modal,
Form
} from 'antd';
import type { ModalProps } from 'antd/lib/modal';
import "antd/dist/antd.css";
const ModalWrap = memo(forwardRef((props: any, ref: any) => {
const [form] = Form.useForm();
const [modalChildren, setModalChildren] = useState<React.ReactElement | null>(null);
const [modalProps, setModalProps] = useState<ModalProps>({
visible: false,
});
const typeRef = useRef<string>();
const onFinish = useCallback((values: any) => {
console.log(values, 'onFinish');
modalProps.onOk && modalProps.onOk(values);
}, [form, modalProps]);
const onClose = React.useCallback(() => {
setModalProps((source: any) => ({
...source,
visible: false,
}));
setModalChildren(null);
}, [form]);
const onOpen = useCallback(() => {
setModalProps((source: any) => ({
...source,
visible: true,
}));
}, [form]);
useImperativeHandle(ref, () => ({
injectChildren: (element: React.ReactElement) => { // 用于注入子组件
setModalChildren(element);
},
injectModalProps: (props: any) => { // 用于注入modal的props
setModalProps((source: any) => ({
...source,
...props,
})
)
},
open: onOpen,
close: onClose,
setFieldsValue: (values: any) => {
form.setFieldsValue(values);
},
setType: (type: string) => {
typeRef.current = type;
}
}), []);
const handleOk = useCallback((e: any) => {
console.log(typeRef?.current, form, form.getFieldsValue(true), 'typeRef?.current');
if (typeRef?.current === 'form') {
form.submit();
} else {
modalProps.onOk && modalProps.onOk(e);
}
}, [form, modalProps]);
return (
<Modal
{...modalProps}
onOk={handleOk}
onCancel={onClose}
>
{modalChildren ?
React.cloneElement(modalChildren, {
form,
onFinish,
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 useModal = () => {
const modalRef = useRef<any>();
const open = ({ children, type, initialValues, ...rest }: openArgType) => {
console.log('open', modalRef);
modalRef?.current?.setType(type);
modalRef?.current?.injectChildren(children);
modalRef?.current?.injectModalProps(rest);
modalRef?.current?.open();
if (initialValues && type === 'form') {
modalRef?.current?.setFieldsValue(initialValues);
}
};
const close = useCallback(() => {
modalRef.current.close();
}, []);
const modal = useMemo(() => ({
open: open,
close: close,
}), []);
const ModalDOM = <ModalWrap ref={modalRef} />;
return [
modal,
ModalDOM,
] as const;
}
export default useModal;
//使用方式
const TestModal = () => {
const [modal, useModal] = useModal();
const [form] = Form.useForm();
const onHandleClick = () => {
modal.open({
title: '打开Modal',
children: <ModalExample form={form}/>,
onOk: (values)=>{
modal.close();
}
})
}
return (
<div>
<Button onClick={onHandleClick}>点击弹窗</Button>
</div>
)
}
export default TestModal;