基于Antd Form组件的二次封装

3,327 阅读2分钟

前言

Antd Form 大部分人都会经常使用,使用方法也很简单,比如让你渲染一个登录表单,你可能会这样写 💁

<Form {...formProps}>
    <Form.Item name="account" label="账号">
       <Input />
    </Form.Item>
    <Form.Item name="password" label="密码">
       <Input />
    </Form.Item>
    <Form.Item> 
       <Button type="primary" htmlType="submit"> Submit </Button> 
    </Form.Item>
</Form>

这是一个很简单的例子。日常开发中应付简单的表单,这样写就可以很好的满足需求和代码规范。但是当你的表单足够的复杂,比如FormItem有十几个,且元素包含一些自定义控件,且很多地方需要共用这些FormItem的时候,如果还这样写,势必增加很多不必要的复制粘贴,而且不好统一管理。那么如何把formItem集中起来,方便管理,方便复用呢 🧐?

实现

这里有一个思路就是把要渲染的Item放在一个公共类中,我们用Map数据结构存储 formItem。Map结构的key就是 formItem 的 name 属性,至于Map的值,我们大致从以下几点分析 (可继续拓展)

  1. props 参考 antd Form.Item 属性 这里会对原始的label和name属性类型进行重新定义
  2. Component 要渲染的组件,可以是antd提供的组件,也可以是自定义组件
  3. componentProps? 组件的属性,任意类型
  4. componentChildren? 组件的子元素,可以是一个方法也可以是一个ReactNode
  5. watcher? 表单依赖的watcher 诸如某个FormItem展示与否依赖其它FormItem

class TestDemo {
   getFormMap(): Map<string, any> {
     const formMap = new Map();
     formMap.set('name', {
          props: {
            name: 'name',
            label: '姓名'
          },
          Component: Input,
          componentProps: {
            maxLength: 20
          },
     });
     formMap.set('job', {
          props: {
            name: 'job',
            label: '职业'
          },
          Component: Select,
          componentProps: {
            placeholder: '请选择'
          },
          componentChildren: <>
               <Select.Option key={'teacher'} value={'teacher'}>
                   教师
               </Select.Option>
               <Select.Option key={'worker'} value={'worker'}>
                   工人
               </Select.Option>
          </>
     });
     ......
    
     return formMap
   }
}

export testBo = new TestDemo()

对antd Form进行封装

import { forwardRef, ReactNode } from 'react';
import {
  Form as CForm,
  FormProps as CFormProps,
  FormItemProps as CFormItemProps,
  FormInstance as CFormInstance
} from 'antd';

interface FormItemProps extends Omit<CFormItemProps, 'name' | 'label'> {
  name: string; // 表单字段名称
  label: string | ReactNode; // 表单 label
}

type FormType = {
  props: Partial<FormItemProps>; 
  Component: any; 
  componentProps?: any; 
  componentChildren?: ReactNode | ((watcher?: typeof CForm.useWatch) => ReactNode); 
  watcher?: typeof CForm.useWatch; 
  filter?: (watcher?: typeof CForm.useWatch) => boolean;
};


interface FormProps<Values = any> extends Omit<CFormProps<Values>, 'form'> {
  form?: CFormInstance<Values>;
  formMap?: Map<string, Partial<FormType>>;
  formOrder?: string[];
  disabledForms?: string[];
}

function getItem(
  form: Partial<FormType> | undefined,
  key: string,
  disabledForms: string[] = []
): ReactNode | null {
  if (!form) {
    return null;
  }
  return (
      <CForm.Item key={key} {...form.props}>
        {form.Component && (
          <form.Component
            key={form.props?.name}
            {...form.componentProps}
            disabled={disabledForms.includes(form.props?.name || '')}
          >
            {typeof form.componentChildren === 'function'
              ? form.componentChildren(form.watcher)
              : form.componentChildren}
          </form.Component>
        )}
      </CForm.Item>
    );
}

const CusTomForm: any = forwardRef<CFormInstance>((props: InnerFormProps, ref) => {
  const { form, formOrder = [], formMap = new Map(), disabledForms = [], ...other } = props;

  return (
    <CForm ref={ref} {...other} form={form}>
      <>
        {formOrder.map(name => {
          return getItem(formMap.get(name), name, disabledForms);
        })}
        {props.children}
      </>
    </CForm>
  );
});

CusTomForm.useForm = CForm.useForm;
CusTomForm.useWatch = CForm.useWatch;
CusTomForm.useFormInstance = CForm.useFormInstance;
CusTomForm.Item = CForm.Item;
CusTomForm.List = CForm.List;
CusTomForm.ErrorList = CForm.ErrorList;
CusTomForm.Provider = CForm.Provider;

const Form: InnerFormInterface = CusTomForm;

export { Form, type FormType, type CFormInstance as FormInstance };

使用方法

使用起来就简单了,引入组件,给出必须参数,如下:

import { Form } from './components'
import { testBo }  from './test.bo.ts'

export function Test(){
    const formMap = testBo.getFormMap()
    const orders = ['name', 'job']
    const [form] = Form.useForm()

    return  (<Form
        form={form}
        formMap={formMap}
        formOrder={orders}
        labelCol={{ span: 4 }}
        labelWrap
        layout='vertical'
        autoComplete='off'
      />)   
}

总结

可以继续延伸以使得此组件支持更为复杂的表单,比如子表单等等。