开始
见到过很多在业务中做表单开发的代码,尤其是在使用三方库比如,antd、element-ui等form组件的时候,大部分都是类似使用官网提供的那种方式使用,对于大型表单又或者说通用型表单构建不友好,也不利于效率。下面我举例一步步说明如何更好的利用TS构建一套通用且类型提示全面的表单组件,用起来傻瓜化,配置化。
这里为什么说用TS而不是JS,是因为当各种工作完备后,我们在写业务的时候,会在类型和编码上极大的方便我们,提升不少效率,比如下面所示,组件只要传入一个formlist,就可以渲染想要的表单,并且表单项都有对应的类型检查和提示,提示能根据泛型精确到表单各种字段:
下面举例大部分人的开发模式(你一定很眼熟,想想自己)
1. antd
2. element-ui
是不是很眼熟,很多人都是这样,就是按照官网的方式,一点点往下面排,尤其是在vue中。
react中想法多点的人可能写个list循环一下,一定程度上解决复制粘贴往下排列的问题,但是都不彻底。
这种工作重复又无趣,表单大型了之后修改ui页面想看清楚都很难,有的甚至一个页面表单都好几百行。
好了不啰嗦,上干货
既然说到TS,那么我们先来说说该怎么去设计我们需要的类型(下面的举例,以antd为例,element组件库原理类似)
怎么去写我们需要的类型工具,看下面
- 我们需要一个类型工具,提取组件的props的类型
type GetComponentProps<T> = T extends React.ComponentType<infer P> ? P : never;
- 建立一个组件的映射类型,为后面生成表单联合类型做准备
type FormItemTypeMap = {
checkbox: typeof ProFormCheckbox;
checkboxGroup: typeof ProFormCheckbox.Group;
date: typeof ProFormDatePicker;
dateRange: typeof ProFormDateRangePicker;
default: typeof ProFormItem;
inputNumber: typeof ProFormDigit;
radio: typeof ProFormRadio;
radioGroup: typeof ProFormRadio.Group;
select: typeof ProFormSelect;
switch: typeof ProFormSwitch;
text: typeof ProFormText;
textArea: typeof ProFormTextArea;
upload: typeof ProFormUploadButton;
uploadDragger: typeof ProFormUploadDragger;
};
- 接着生成表用使用的联合类型
type FormItemTypeMapKeys = keyof FormItemTypeMap;
type FormItemRenderUnique = {
[K in FormItemTypeMapKeys]: { type: K } & GetComponentProps<FormItemTypeMap[K];
}[FormItemTypeMapKeys];
- 最终组合自己想要的表单类型,比如
type CreateFormItem<T extends Record<string, any> = any> = {
key?: React.Key;
label: React.ReactNode;
placeholder?: string;
name?:string;
...
} & FormRenderItemUnique;
这样通过type的交叉获得了所有需要的业务表单组件的联合类型,后面我们会用到。
类型工具完成后,接下来怎么构建业务工具函数,当然可以使用各种设计模式,各位大神尽情发挥就是了,我这里抛砖引玉
这里我们就用到了CreateFormItem这个类型了,仔细看getFormItem的参数类型,这里提供的示例就是一个获得formItem的工具函数
const getFormItem = <T extends Record<string, any>>(params: CreateFormItem<T>) => {
const { type, children, dependOptions, groupKey, key, ...rest } = params;
if (!rest?.placeholder) rest.placeholder = `请输入${rest.label}`;
const _key = key ?? params.name;
switch (type) {
case 'text':
return <ProFormText key={_key} {...(rest as GetReactComponentProps<RenderItemTypeMap['text']>)} />;
case 'select':
return <ProFormSelect key={_key} {...(rest as GetReactComponentProps<RenderItemTypeMap['select']>)} />;
... ...
default:
return (
<ProFormItem key={_key} {...(rest as GetReactComponentProps<RenderItemTypeMap['default']>)}>
{children}
</ProFormItem>
);
}
};
然后我们只需要写一个创建自定义的CustomFormItem的组件,里面可能有某些自己对应的逻辑,或者说直接使用getFormItem工具函数像这样就可以了,后面我们在业务中使用表单的时候,就可以做到像开头的视频中的那样,只需要构建一个formlist,就能构建任意的表单。
function CustomCreateForm<T extends Record<string, any> = any>(
props: { formList: CreateFormItem<T>[] } & GetReactComponentProps<typeof ProForm>,
) {
const { formList, ...rest } = props;
return (
<ProForm {...rest}>
{formList.map((item) => (
<CustomFormItem itemData={item} />
))}
</ProForm>
);
}
function CustomCreateForm<T extends Record<string, any> = any>(
props: { formList: CreateFormItem<T>[] } & GetReactComponentProps<typeof ProForm>,
) {
const { formList, ...rest } = props;
return (
<ProForm {...rest}>
{formList.map((item) => (
getFormItem(item)
))}
</ProForm>
);
}
比如下面就是我们业务当中各种场景用的某一种: