背景是个常见的需求,表格场景中的常见crud交互会涉及这几个元素:
- 新增按钮
- 表格row中的 编辑按钮
- 新增用的form表单弹窗
- 编辑用的form表单弹窗
每次重复写modal的visible以及复显旧值,让我十分恶心🤢,所以封装了一个hooks,来解决这个问题。
demo
import useModalForm from '@/utils/useModalForm';
import {
ModalForm,
ProFormDatePicker,
ProFormText,
} from '@ant-design/pro-components';
import { Button, Space, Form, Divider } from 'antd';
import * as React from 'react';
export interface ModalFormDemoProps {
children: any;
}
const ModalFormDemo: React.FC<ModalFormDemoProps> = () => {
const [form] = Form.useForm();
const formModalCtx = useModalForm({
onCancel() {
form.resetFields();
},
trigger() {
return <Button>新建</Button>;
},
editTrigger({ entity }) {
return (
<Button
onClick={() => {
formModalCtx.setEntity(entity);
form.resetFields();
form.setFieldsValue(entity);
}}
>
编辑 {entity.name}
</Button>
);
},
modal({ entity }) {
const txt = entity ? '编辑' : '新建';
return (
<ModalForm
title={`${txt}表单`}
form={form}
onFinish={async (values) => {
if (entity) {
console.log('编辑接口', entity, values);
} else {
console.log('新建接口', values);
}
}}
>
<ProFormText label="名称" name="name" />
<ProFormDatePicker label="日期" name="date" disabled={!!entity} />
</ModalForm>
);
},
});
return (
<div>
<Space>{formModalCtx.renderTrigger()}</Space>
<Divider />
{
// 模拟表格列表等情况中接口获取的数据
[
{ id: 1, name: '原名称', date: '2023-01-01' },
{ id: 2, name: '另一个', date: '2023-02-01' },
].map((item) => (
<div>{formModalCtx.renderEditTrigger({ entity: item })}</div>
))
}
{formModalCtx.renderModal()}
</div>
);
};
export default ModalFormDemo;
ui
新建时:
编辑时:
最后表单提交时拿到的数据:
useModalForm.tsx
import * as React from 'react';
import { useCallback, useRef, useState } from 'react';
interface Params<E> {
modal: (params: { entity: E | null }) => React.ReactElement;
trigger: (any?: any) => React.ReactElement;
editTrigger?: (any?: any) => React.ReactElement;
onCancel?: () => void;
isUseOnCancel?: boolean;
}
function useModalForm<Entity = any>({
modal,
trigger,
editTrigger,
onCancel,
isUseOnCancel = true,
}: Params<Entity>) {
const [open, setOpen] = useState(false);
const entity = useRef<any>(null);
const setEntity = useCallback((p) => (entity.current = p), []);
const onVisibleChange = (visible: boolean) => {
if (!visible) {
setEntity(null);
onCancel?.();
}
setOpen(visible);
};
const renderTrigger = (any: any = {}) => {
// @ts-ignore
const dom = trigger({ ...any });
return React.cloneElement(dom, {
...dom.props,
onClick: async (e: any) => {
setOpen((s) => !s);
dom.props?.onClick?.(e);
},
className: `${dom.props.className || ''} trigger`,
});
};
const renderEditTrigger = (any: any = {}) => {
if (!editTrigger) {
return null;
}
// @ts-ignore
const dom = editTrigger({ ...any });
return React.cloneElement(dom, {
...dom.props,
onClick: async (e: any) => {
setOpen((s) => !s);
dom.props?.onClick?.(e);
},
className: `${dom.props.className || ''} trigger`,
});
};
const renderModal = (rest: any = {}) => {
// @ts-ignore
const dom = modal({ entity: entity.current, ...rest });
const newProps = {
...dom.props,
open,
visible: open,
onVisibleChange,
onCancel() {},
};
if (isUseOnCancel) {
newProps.onCancel = () => {
setEntity(null);
onCancel?.();
setOpen(false);
};
}
return React.cloneElement(dom, newProps);
};
return {
renderModal,
renderTrigger,
renderEditTrigger,
setEntity,
};
}
export default useModalForm;
useModalForm可以用在类似的场景当中。我在demo中使用了@ant-design/pro-components的ModalForm,不得不说真香。用普通的modal,稍稍封装的兼容逻辑也是一样可以的。
或许有的朋友会觉得为了每次少写下visible变量,整这么麻烦没必要。我是这样想的,这一套表格的crud逻辑十分常见,而且流程十分的类似。封装重复逻辑,不仅是每次少写点代码,而且可以在写代码时更关注于业务本身,这种重复逻辑可以有一种一笔带过的顺畅感。
另外考虑的一个问题是,我参考了ahooks库以及其他关于hooks的文章,都没有见到用hooks返回render的写法。我自己的理解,render也是一种函数,理论上应该完全可以封装进hooks。
这个版本是刚出炉的版本,大家给点意见。