前言
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的值,我们大致从以下几点分析 (可继续拓展)
- props 参考 antd Form.Item 属性 这里会对原始的label和name属性类型进行重新定义
- Component 要渲染的组件,可以是antd提供的组件,也可以是自定义组件
- componentProps? 组件的属性,任意类型
- componentChildren? 组件的子元素,可以是一个方法也可以是一个ReactNode
- 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'
/>)
}
总结
可以继续延伸以使得此组件支持更为复杂的表单,比如子表单等等。