通过手写hooks的方式更优雅的使用antd Modal

1,538 阅读2分钟

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;

参考链接:www.jianshu.com/p/a26a73dbd…