# formily 表单初识
# 背景
Formily 是 alibaba 开源表单框架,抽象了表单领域模型的 MVVM 表单解决方案。
这里引用官网的介绍:
众所周知,表单场景一直都是前端中后台领域最复杂的场景,它的复杂度主要在哪里呢?
字段数量多,如何让性能不随字段数量增加而变差?
字段关联逻辑复杂,如何更简单的实现复杂的联动逻辑?字段与字段关联时,如何保证不影响表单性能?
- 一对多 (异步)
- 多对一 (异步)
- 多对多 (异步)
表单数据管理复杂
- 表单值转换逻辑复杂 (前后端格式不一致)
- 同步默认值与异步默认值合并逻辑复杂
- 跨表单数据通信,如何让性能不随字段数量增加而变差?
表单状态管理复杂
- 着重提自增列表场景,如何让数组数据在移动,删除过程中,字段状态能够做到跟随移动?
表单的场景化复用
- 查询列表
- 弹窗 / 抽屉表单
- 分步表单
- 选项卡表单
动态渲染诉求很强烈
- 字段配置化,让非专业前端也能快速搭建复杂表单
- 跨端渲染,一份 JSON Schema,多端适配
- 如何在表单协议中描述布局?
- 纵向布局
- 横向布局
- 网格布局
- 弹性布局
- 自由布局
- 如何在表单协议中描述逻辑?
# 快速开始
对于 react
用户来说, antd
是应用最为广泛的 UI 库之一。 @formily/antd-v5
是基于 Ant Design V5
封装的针对表单场景专业级组件库。
npm install --save antd dayjs | |
npm install --save @formily/core @formily/react @formily/antd-v5 |
# JSON Schema 渲染模式
Formily
支持 3 种渲染模式, JSX
, Markup Schema
和 JSON Schema
。这里主要介绍最后一种 JSON Schema
Formily
提供了一套 DSL
,可以通过 JSON 渲染表单结构。
下面是一个最简的 formily
定义的 JSON schema
,以下 JSON
便可描述 form
的结构。
const normalSchema = { | |
type: "object", | |
properties: { | |
username: { | |
type: "array", | |
title: "Username", | |
required: true, | |
"x-decorator": "FormItem", | |
"x-component": "Input", | |
'x-component-props': { | |
prefix: "{{icon('UserOutlined')}}", | |
}, | |
}, | |
password: { | |
type: "string", | |
title: "Password", | |
required: true, | |
"x-decorator": "FormItem", | |
"x-component": "Password", | |
'x-component-props': { | |
prefix: "{{icon('LockOutlined')}}", | |
}, | |
} | |
} | |
}; |
有了 json schema
定义的表格结构之后,要创建出表格还需要如下的操作
const SchemaField = createSchemaField({ | |
components: { | |
FormItem, | |
Input, | |
Password, | |
ArrayCards | |
}, | |
scope: { | |
icon(name: string) { | |
return React.createElement(ICONS[name]); | |
} | |
} | |
}); | |
const form = createForm(); | |
export default function App() { | |
return ( | |
<div className="App"> | |
<FormProvider form={form}> | |
<SchemaField schema={normalSchema} /> | |
<FormButtonGroup> | |
<Submit onSubmit={console.log}>提交</Submit> | |
</FormButtonGroup> | |
</FormProvider> | |
</div> | |
); | |
} |
其生成的表格如下表所示,源代码地址:formilyTest - CodeSandbox
# SchemaField
SchemaField 组件是专门用于解析 JSON-Schema 动态渲染表单的组件。 在使用 SchemaField 组件的时候,需要通过 createSchemaField 工厂函数创建一个 SchemaField 组件。
其接收两个参数 , components
对应内部需要用到的组件,可以是 antd
的组件,也可以是自定义的组件。 scope
对应的是注入的变量,比如国际化时,可以将 locale
等传入。
components?: Components; | |
scope?: any; |
# 使用场景
# 自定义校验
很多时候需要自定义校验规则和校验文案,
那么首先看一下预设的校验规则
// 字符串型格式校验器 | |
type ValidatorFormats = | |
| 'url' | |
| 'email' | |
| 'ipv6' | |
| 'ipv4' | |
| 'number' | |
| 'integer' | |
| 'idcard' | |
| 'qq' | |
| 'phone' | |
| 'money' | |
| 'zh' | |
| 'date' | |
| 'zip' | |
| (string & {}) // 其他格式校验器需要通过 registerValidateFormats 进行注册 | |
// 对象型校验结果 | |
interface IValidateResult { | |
type: 'error' | 'warning' | 'success' | (string & {}) | |
message: string | |
} | |
// 对象型校验器 | |
interface IValidatorRules<Context = any> { | |
triggerType?: 'onInput' | 'onFocus' | 'onBlur' | |
format?: ValidatorFormats | |
validator?: ValidatorFunction<Context> | |
required?: boolean | |
pattern?: RegExp | string | |
max?: number | |
maximum?: number | |
exclusiveMaximum?: number | |
exclusiveMinimum?: number | |
minimum?: number | |
min?: number | |
len?: number | |
whitespace?: boolean | |
enum?: any[] | |
const?: any | |
multipleOf?: number | |
uniqueItems?: boolean | |
maxProperties?: number | |
minProperties?: number | |
maxItems?: number | |
maxLength?: number | |
minItems?: number | |
minLength?: number | |
message?: string | |
[key: string]: any // 其他属性需要通过 registerValidateRules 进行注册 | |
} | |
// 函数型校验器校验结果类型 | |
type ValidatorFunctionResponse = null | string | boolean | IValidateResult | |
// 函数型校验器 | |
type ValidatorFunction<Context = any> = ( | |
value: any, | |
rule: IValidatorRules<Context>, | |
ctx: Context | |
) => ValidatorFunctionResponse | Promise<ValidatorFunctionResponse> | null | |
// 非数组型校验器 | |
type ValidatorDescription = | |
| ValidatorFormats | |
| ValidatorFunction<Context> | |
| IValidatorRules<Context> | |
// 数组型校验器 | |
type MultiValidator<Context = any> = ValidatorDescription<Context>[] | |
type FieldValidator<Context = any> = | |
| ValidatorDescription<Context> | |
| MultiValidator<Context> |
这里首先考虑如下的校验规则,错误提示有两种 自定义非空校验规则
和 固定值校验
,并且都有对应的 message
信息,那么其 schema
可由此给出
const schema: ISchema = { | |
type: 'object', | |
properties: { | |
input: { | |
type: 'string', | |
title: '输入框', | |
'x-decorator': 'FormItem', | |
'x-component': 'Input', | |
'x-component-props': { | |
style: { | |
width: 240, | |
}, | |
}, | |
'x-validator':[ | |
{ | |
required: true, | |
message:'自定义非空校验规则' | |
}, | |
{ | |
validator: `{{(value, rule)=> { if (!value) return '' return value !== '123' ? '请填写123' : '' }}}`, | |
}] | |
}, | |
textarea: { | |
type: 'string', | |
title: '文本框', | |
required: true, | |
'x-decorator': 'FormItem', | |
'x-component': 'Input.TextArea', | |
'x-component-props': { | |
style: { | |
width: 400, | |
}, | |
}, | |
}, | |
}, | |
} |
我们可以看一下对应的效果
可以看到上图右边出现两种校验错误信息,其对应的验证器为如下所示,
'x-validator':[ | |
{ | |
required: true, | |
message:'自定义非空校验规则' | |
}, | |
{ | |
validator: `{{(value, rule)=> { if (!value) return '请填写123' return value !== '123' ? '请填写123' : '' }}}`, | |
}] |
通过这个也可以看出,多个验证器存在时,错误信息也可能会存在多个,写的时候需要进行额外注意。