ProForm 高级表单

ProForm 在原来的 Form 的基础上增加一些语法糖和更多的布局设置,帮助我们快速的开发一个表单。同时添加一些默认行为,让我们的表单默认好用。

分步表单,Modal 表单,Drawer 表单,查询表单,轻量筛选等多种 layout 可以覆盖大部分的使用场景,脱离复杂而且繁琐的表单布局工作,更少的代码完成更多的功能。

  • 如果你想要设置默认值,请使用 initialValues,任何直接使用组件 value 和 onChange 的方式都有可能导致值绑定失效。
  • 如果想要表单联动或者做一些依赖,可以使用 render props 模式, ProFormDependency 绝对是最好的选择
  • ProForm 的 onFinish 与 antd 的 Form 不同,是个 Promise,如果你正常返回会自动为你设置按钮的加载效果
  • 如果想要监听某个值,建议使用 onValuesChange。保持单向的数据流无论对开发者还是维护者都大有裨益
  • ProForm 没有黑科技,只是 antd 的 Form 的封装,如果要使用自定义的组件可以用 Form.Item 包裹后使用,支持混用
// 设置整体默认值
<ProForm initialValues={obj} />
// 设置单个控件的
<ProForm
onValuesChange={(changeValues) => console.log(changeValues)}
>
<ProFormText initialValue="prop"/>
</ProForm>
// 相互依赖的组件联动
<ProForm>
<Form.Item noStyle shouldUpdate>
{(form) => {
return (
<ProFormSelect
options={[
{
value: "chapter",
label: "盖章后生效",
},
]}
width="md"
name="useMode"
label={`与${form.getFieldValue("name")}合同约定生效方式`}
/>
);
}}
</Form.Item>
</ProForm>;
// 使用自定义组件
<ProForm>
<Form.Item name="switch" label="Switch" valuePropName="checked">
<Switch />
</Form.Item>
</ProForm>

何时使用 ProForm?

当你想快速实现一个表单但不想花太多时间去布局时 ProForm 是最好的选择。

ProForm 是基于 antd Form 的可降级封装,与 antd 功能完全对齐,但是在其之上还增加一些预设行为和多种布局。这些布局之间可以无缝切换,并且拥有公共的 API。

布局使用场景
ProForm标准 Form,增加了 onFinish 中自动 loading 和 根据 request 自动获取默认值的功能。
ModalForm|DrawerForm在 ProForm 的基础上增加了 trigger ,无需维护 visible 状态
QueryFilter一般用于作为筛选表单,需要配合其他数据展示组件使用
LightFilter一般用于作为行内内置的筛选,比如卡片操作栏和 表格操作栏。
StepsForm分步表单,需要配置 StepForm 使用。

数据转化

很多时候组件需要的数据和后端需要的数据之间不能完全匹配,ProForm 为了解决这个问题提供了 transform 和 convertValue 两个 API 来处理这种情况。

convertValue 前置转化

convertValue 发生在组件获得数据之前,一般是后端直接给前端的数据,有时需要精加工一下。

export type SearchConvertKeyFn = (value: any, field: NamePath) => string | Record<string, any>;
/**
* @name 获取时转化值,一般用于将数据格式化为组件接收的格式
* @param value 字段的值
* @param namePath 字段的name
* @returns 字段新的值
*
*
* @example a,b => [a,b] convertValue: (value,namePath)=> value.split(",")
* @example string => json convertValue: (value,namePath)=> JSON.parse(value)
* @example number => date convertValue: (value,namePath)=> Moment(value)
* @example YYYY-MM-DD => date convertValue: (value,namePath)=> Moment(value,"YYYY-MM-DD")
* @example string => object convertValue: (value,namePath)=> { return {value,label:value} }
*/
convertValue?: SearchConvertKeyFn;

transform 提交时转化

transform 发生在提交的时候,一般来说都是吐给后端的存在数据库里的数据。

为了方便大家使用,ProFormDependency 和 formRef 都支持了 transform,可以获取到被转化后的值。

<ProFormDependency>
{(value, form) => {
// value 被 transform转化之后的值
// form 当前的formRef,可以获取未转化的值
return ReactNode;
}}
</ProFormDependency>

formRef 内置了几个方法来获取转化之后的值,这也是相比 antd 的 Form 多的功能,详细可以看 ProFormInstance 的类型定义。

/** 获取被 ProForm 格式化后的所有数据 */
getFieldsFormatValue?: (nameList?: true) => T;
/** 获取格式化之后的单个数据 */
getFieldFormatValue?: (nameList?: NamePath) => T;
/** 获取格式化之后的单个数据 */
getFieldFormatValueObject?: (nameList?: NamePath) => T;
/** 验字段后返回格式化之后的所有数据*/
validateFieldsReturnFormatValue?: (nameList?: NamePath[]) => Promise<T>;
export type SearchTransformKeyFn = (
value: any,
namePath: string,
allValues: any,
) => string | Record<string, any>;
/**
* @name 提交时转化值,一般用于将值转化为提交的数据
* @param value 字段的值
* @param namePath 字段的name
* @param allValues 所有的字段
* @returns 字段新的值,如果返回对象,会和所有值 merge 一次
*
* @example {name:[a,b] => {name:a,b } transform: (value,namePath,allValues)=> value.join(",")
* @example {name: string => { newName:string } transform: (value,namePath,allValues)=> { newName:value }
* @example {name:moment} => {name:string transform: (value,namePath,allValues)=> value.format("YYYY-MM-DD")
* @example {name:moment}=> {name:时间戳} transform: (value,namePath,allValues)=> value.valueOf()
* @example {name:{value,label}} => { name:string} transform: (value,namePath,allValues)=> value.value
* @example {name:{value,label}} => { valueName,labelName } transform: (value,namePath,allValues)=> { valueName:value.value, labelName:value.name }
*/
transform?: SearchTransformKeyFn;

代码示例

基本使用

标签与表单项布局

除了 LightFilter 和 QueryFilter 这样固定布局的表单样式,其他表单布局支持配置与 antd 一致的三种布局方式。

栅格化布局

同时支持在 ProForm, SchemaForm, ModalForm, DrawerForm, StepsForm 中使用

表单联动

表单方法调用

你可以通过 formRef 获取到表单实例的引用,通过引用可以调用表单方法实现表单重置,设置表单,获取表单值等功能。

同步提交结果到 url

打开时也会把 url 的参数设置为默认值,支持 transform, 但是要注意字段的映射。

金额

固定页脚

ProForm 和 EditableTable 同时使用

ProForm

ProForm 是 antd Form 的再封装,如果你想要自定义表单元素,ProForm 与 antd 的方法是相同的,你仍然可以用 FormItem + 自定义组件的方式来自定义。当然这样不会影响到别的组件,QueryFilter 等组件同理。

antd 的 Form api 查看这里 initialValues 相关知识查看这里

参数说明类型默认值
onFinish提交表单且数据验证成功后回调事件,同 antd 4 Form 组件 API(values)=>Promise<void>-
onReset点击重置按钮的回调(e)=>void-
submitter提交按钮相关配置boolean | SubmitterPropstrue
syncToUrl同步参数到 url 上,url 只支持 string,在使用之前最好读一下url 中的参数类型true | (values,type)=>values-
syncToInitialValues同步结果到 initialValues,默认为 true 如果为 false,form.reset 的时将会忽略从 url 上获取的数据booleantrue
dateFormatter自动格式数据,主要是 moment 的表单,支持 string 和 number 两种模式,此外还支持指定函数进行格式化。string| number | ((value: Moment, valueType: string) => string | number) | falsestring
omitNilProForm 会自动清空 null 和 undefined 的数数据,如果你约定了 nil 代表某种数据,可以设置为 false 关闭此功能booleantrue
params发起网络请求的参数,与 request 配合使用Record-
request发起网络请求的参数,返回值会覆盖给 initialValues(params)=>Promise<data>-
isKeyPressSubmit是否使用回车提交boolean-
formRef获取表单所使用的 formReact.MutableRefObject<ProFormInstance<T>>-
autoFocusFirstInput自动 focus 表单第一个输入框boolean-
grid开启栅格化模式,宽度默认百分比,请使用 colProps 控制宽度 查看示例boolean-
rowProps开启 grid 模式时传递给 Row, 仅在ProFormGroup, ProFormList, ProFormFieldSet 中有效RowProps{ gutter: 8 }
(...)注意 LightFilter 和 QueryFilter 仅支持除 wrapperCol | labelCol | layout 外的其他 antd Form 组件参数--

ProFormInstance

ProFormInstance 与 antd 的 form 相比增加了一些能力。

/**
* 获取被 ProForm 格式化后的所有数据
* @param nameList boolean
* @returns T
*
* @example getFieldsFormatValue() ->返回所有数据
* @example getFieldsFormatValue(true) ->返回所有数据,即使没有被 form 托管的
*/
getFieldsFormatValue?: (nameList?: true) => T;
/**
* 获取被 ProForm 格式化后的单个数据
* @param nameList (string|number)[]
* @returns T
*
* @example {a:{b:value}} -> getFieldFormatValue(['a', 'b']) -> value
*/
/** 获取格式化之后的单个数据 */
getFieldFormatValue?: (nameList?: NamePath) => T;
/**
* 获取被 ProForm 格式化后的单个数据, 包含他的 name
* @param nameList (string|number)[]
* @returns T
*
* @example {a:{b:value}} -> getFieldFormatValueObject(['a', 'b']) -> {a:{b:value}}
*/
/** 获取格式化之后的单个数据 */
getFieldFormatValueObject?: (nameList?: NamePath) => T;
/**
*验字段后返回格式化之后的所有数据
* @param nameList (string|number)[]
* @returns T
*
* @example validateFieldsReturnFormatValue -> {a:{b:value}}
*/
validateFieldsReturnFormatValue?: (nameList?: NamePath[]) => Promise<T>;

ProForm.Group

参数说明类型默认值
title标题string-
children表单控件或者其他元素React.ReactNode-

submitter

虽然我们希望不要对 submitter 进行修改,但在使用中修改是很常见的需求,ProForm 的各个组件都使用了同样的 API 来支持需求。

参数说明类型默认值
onSubmit提交方法()=>void-
onReset重置方法()=>void-
searchConfig搜索的配置,一般用来配置文本{resetText,submitText}-
submitButtonProps提交按钮的 propsButtonProps-
resetButtonProps重置按钮的 propsButtonProps-
render自定义操作的渲染false|(props,dom:JSX[])=>ReactNode[]-

render 的第二个参数是默认的 dom 数组,第一个是重置按钮,第二个是提交按钮。

<ProForm
submitter={{
// 配置按钮文本
searchConfig: {
resetText: '重置',
submitText: '提交',
},
// 配置按钮的属性
resetButtonProps: {
style: {
// 隐藏重置按钮
display: 'none',
},
},
submitButtonProps: {},
// 完全自定义整个区域
render: (props, doms) => {
console.log(props);
return [
<button type="button" key="rest" onClick={() => props.form?.resetFields()}>
重置
</button>,
<button type="button" key="submit" onClick={() => props.form?.submit?.()}>
提交
</button>,
];
},
}}
/>

formRef

该属性是 ProForm 在原有的 Antd 的 FormInstance 的基础上做的一个上层分装,增加了一些更加便捷的方法。使用方式如下:

import type { ProFormInstance } from '@ant-design/pro-components';
import { ProForm, ProFormDatePicker, ProFormText } from '@ant-design/pro-components';
import { Button, message } from 'antd';
import moment from 'moment';
import { useRef } from 'react';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
export default () => {
const formRef = useRef<ProFormInstance>();
const onFill = () => {
formRef?.current?.setFieldsValue({
name: '张三',
company: '蚂蚁金服',
});
};
const getCompanyName = () => {
message.info(`公司名称为 "${formRef?.current?.getFieldValue('company')}"`);
};
const getFormatValues = () => {
console.log('格式化后的所有数据:', formRef.current?.getFieldsFormatValue?.());
};
const validateAndGetFormatValue = () => {
formRef.current?.validateFieldsReturnFormatValue?.().then((values) => {
console.log('校验表单并返回格式化后的所有数据:', values);
});
};
return (
<ProForm
title="新建表单"
formRef={formRef}
submitter={{
render: (props, doms) => {
return [
...doms,
<Button htmlType="button" onClick={onFill} key="edit">
一键填写
</Button>,
<Button htmlType="button" onClick={getCompanyName} key="read">
读取公司
</Button>,
<Button.Group key="refs" style={{ display: 'block' }}>
<Button htmlType="button" onClick={getFormatValues} key="format">
获取格式化后的所有数据
</Button>
<Button htmlType="button" onClick={validateAndGetFormatValue} key="format">
校验表单并返回格式化后的所有数据
</Button>
</Button.Group>,
];
},
}}
onFinish={async (values) => {
await waitTime(2000);
console.log(values);
message.success('提交成功');
return true;
}}
>
<ProFormText
width="md"
name="name"
label="签约客户名称"
tooltip="最长为 24 位"
placeholder="请输入名称"
/>
<ProFormText width="md" name="company" label="我方公司名称" placeholder="请输入名称" />
<ProFormDatePicker name="date" initialValue={moment('2021-08-09')} />
</ProForm>
);
};

ProFormInstance在原先FormInstance的基础上增加了如下方法:

方法名使用描述备注
getFieldsFormatValue使用方法与FormInstance的getFieldsValue方法相同,将返回格式化后的所有数据
getFieldFormatValue使用方法与FormInstance的getFieldValue方法相同,将返回格式化后的指定数据
validateFieldsReturnFormatValue使用方法与FormInstance的validateFields方法相同,验证通过后将返回格式化后的所有数据