一個(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我們就寫(xiě)了將近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}
</>
)
}
上面我們寫(xiě)的預(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', // 類(lèi)型為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)雅多了。