最近开发一个全新的项目,由于项目时间比较紧张,选用了开箱直用的antd ProComponents来开发表单,引发了一些小小思考,在此记录一下📝~
一、浅谈表单封装
作为前端人儿,一般都对表单再熟悉不过了。如果直接用 Form.Item去写每一个表单项,不仅代码臃肿,而且不利于复用。对于开发有一定设计规范(具有比较稳定的风格和预设行为)的软件系统而言,都是可以二次封装表单,再通过写json的形式配置出批量表单的。

二、谈论使用SchemaForm的感受和学习到的地方
1、之前用material ui封装表单的问题
① 子组件核心表单项的props平铺展开暴露给父组件,与其他配置项属性混杂,不利于后期维护;
② 表单项没有配置slot,不利于拓展;
③ 没有向顶层组件暴露form实例的属性及方法,单纯依靠定义多个setState 获取所有表单数值、当前变化表单数据、提交时校验时的首个错误表单项等,数据一层层传递很麻烦;
④ 多层组件的字段和单层组件定义不同,无法直接简单配置出混合单层及多层的表单
······
2、从使用SchemaForm引发的一些思考
SchemaForm 是根据 JSON Schema 来生成表单的工具。SchemaForm 会根据 valueType 来映射成不同的表单项。
封装组件时要注重可拓展性,支持上层组件灵活调整显示效果,以支持更多更复杂的需求;
- 枚举类的配置项不要写死,尽量支持自定义。如SchemaForm依赖valueType映射表单项,不仅自定义了一些预设type,还支持自定义。
- 预留slot/planB,支持自定义不同条件下的展示组件。如SchemaForm定义了renderFormItem,自定义编辑模式,返回一个 ReactNode,会自动包裹 value 和 onChange。
- 定义更灵活的数据结构,支持map数据类型/多层数组等。如SchemaForm的columns支持多重"套娃🪆🪆",valueType也有group类型,支持简单配置出多层复杂的表单组件;
举个官网上的🌰
js
const columns = [
{
title: '列表',
valueType: 'formList',
dataIndex: 'list',
initialValue: [{ state: 'all', title: '标题' }],
columns: [
{
valueType: 'group',
columns: [
{
title: '状态',
dataIndex: 'state',
valueType: 'select',
width: 'xs',
valueEnum,
},
{
title: '标题',
dataIndex: 'title',
formItemProps: {
rules: [
{
required: true,
message: '此项为必填项',
},
],
},
width: 'm',
},
],
},
],
},
];
三、基于SchemaForm封装更符合项目特点的表单--思路分享
由于antd-pro的SchemaForm在数据联动、数据转化、数据校验、样式布局、复杂表单项显示等各个方面都预设了很多行为,一般这些方面直接拿来用即可,所以基于SchemaForm封装表单,我们一般要做的就是结合项目特点做一些统一配置,如
- 自行封装其他valueType方便快速使用;
- 在columns传递给SchemaForm时做一些统一处理以免去重复配置,注意仍要支持上层组件自定义。
代码示例🌰
- 定义ProFormProvider组件,运用useContext预设更多valueType,以供不同外层容器中的表单使用。
tsx
import { useContext } from 'react';
import { ProProvider } from '@ant-design/pro-provider';
import { Counter, InputWithSelectBefore, RadioWithInput } from './components';
import { ValueTypes } from '.';
import { ProFormProviderContext } from './context';
export const ProFormProvider = (props) => {
const { children, valueTypeMap = {}, formRef, ...rest } = props;
const providerValues = useContext(ProProvider);
return (
<ProFormProviderContext.Provider value={{ formRef, ...rest }}>
<ProProvider.Provider
value={{
...providerValues,
valueTypeMap: {
...valueTypeMap,
[ValueTypes.counter]: {
render: (text) => (text),
renderFormItem: (text, props, dom) => (
<Counter {...props} {...props?.fieldProps} />
),
},
}
}}>
{children}
</ProProvider.Provider>
</ProFormProviderContext.Provider>
);
};
- 定义自定义表单项组件,关注value和onChange
可参考官网示例🌰------自定义valueType
- 外层直接使用的SchemaForm组件,注意预设配置要支持上层组件修改噢
tsx
import { useMemo, memo } from 'react';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { ProFormProvider } from '@/components';
import { useTranslation } from 'react-i18next';
import { transformColumns } from './util';
const SchemaForm = (props) => {
const {
layoutType = 'Form',
size = 'large',
initialValues,
columns,
formRef,
valueTypeMap,
submitter = false,
onFinish,
onValuesChange,
...rest
} = props;
const { t } = useTranslation();
// 利用transformColumns方法做一些统一处理。
const _columns = useMemo(() => {
return transformColumns(columns, t);
}, [columns]);
return (
<ProFormProvider valueTypeMap={valueTypeMap} formRef={formRef} >
<BetaSchemaForm
autoFocusFirstInput={false}
formRef={formRef}
layoutType={layoutType}
size={size}
grid={true}
rowProps={{ gutter: [30, 0] }}
labelCol={{ flex: '0 0 40px' }}
initialValues={initialValues}
columns={_columns}
submitter={submitter}
onFinish={async (values) => {
onFinish && onFinish(values);
}}
onValuesChange={(changeValues, values) => {
onValuesChange && onValuesChange(changeValues, values);
}}
{...rest}
/>
</ProFormProvider>
);
};
四、参考文章
-
FormRender 2.0 开箱即用表单方案,助你准点下班 ----可参考组件自定义方法
-
学会正确地二次封装组件,让同事抱着你的大腿喊大神! ----学习一些封装组件思路