业务中如何使用TS快速构建form表单

427 阅读3分钟

开始

见到过很多在业务中做表单开发的代码,尤其是在使用三方库比如,antd、element-ui等form组件的时候,大部分都是类似使用官网提供的那种方式使用,对于大型表单又或者说通用型表单构建不友好,也不利于效率。下面我举例一步步说明如何更好的利用TS构建一套通用且类型提示全面的表单组件,用起来傻瓜化,配置化。

这里为什么说用TS而不是JS,是因为当各种工作完备后,我们在写业务的时候,会在类型和编码上极大的方便我们,提升不少效率,比如下面所示,组件只要传入一个formlist,就可以渲染想要的表单,并且表单项都有对应的类型检查和提示,提示能根据泛型精确到表单各种字段

o3lb4-4tqh0.gif

下面举例大部分人的开发模式(你一定很眼熟,想想自己)

1. antd

image.png

2. element-ui

image.png 是不是很眼熟,很多人都是这样,就是按照官网的方式,一点点往下面排,尤其是在vue中。 react中想法多点的人可能写个list循环一下,一定程度上解决复制粘贴往下排列的问题,但是都不彻底。 这种工作重复又无趣,表单大型了之后修改ui页面想看清楚都很难,有的甚至一个页面表单都好几百行。

好了不啰嗦,上干货

既然说到TS,那么我们先来说说该怎么去设计我们需要的类型(下面的举例,以antd为例,element组件库原理类似)

怎么去写我们需要的类型工具,看下面

  1. 我们需要一个类型工具,提取组件的props的类型
type GetComponentProps<T> = T extends React.ComponentType<infer P> ? P : never;
  1. 建立一个组件的映射类型,为后面生成表单联合类型做准备
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;
};
  1. 接着生成表用使用的联合类型
type FormItemTypeMapKeys = keyof FormItemTypeMap;

type FormItemRenderUnique = {
  [K in FormItemTypeMapKeys]: { type: K } & GetComponentProps<FormItemTypeMap[K];
}[FormItemTypeMapKeys];
  1. 最终组合自己想要的表单类型,比如
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>
  );
}

比如下面就是我们业务当中各种场景用的某一种:

22222.png

结束