1. Formily 是什么?
Formily 是 alibaba 开源表单框架,抽象了表单领域模型的 MVVM 表单解决方案。
这里引用官网的介绍:
众所周知,表单场景一直都是前端中后台领域最复杂的场景,它的复杂度主要在哪里呢?
- 字段数量多,如何让性能不随字段数量增加而变差?
- 字段关联逻辑复杂,如何更简单的实现复杂的联动逻辑?字段与字段关联时,如何保证不影响表单性能?
- 表单数据管理复杂
- 表单状态管理复杂
- 表单的场景化复用
- 动态渲染诉求很强烈
这张图主要将 Formily 分为了内核层,UI 桥接层,扩展组件层,和配置应用层。
内核层是 UI 无关的,它保证了用户管理的逻辑和状态是不耦合任何一个框架,这样有几个好处:
- 逻辑与 UI 框架解耦,未来做框架级别的迁移,业务代码无需大范围重构
- 学习成本统一,如果用户使用了
@formily/react
,以后业务迁移@formily/vue
,用户不需要重新学习
JSON Schema 独立存在,给 UI 桥接层消费,保证了协议驱动在不同 UI 框架下的绝对一致性,不需要重复实现协议解析逻辑。
扩展组件层,提供一系列表单场景化组件,保证用户开箱即用。无需花大量时间做二次开发。
2. 核心优势
- 高性能
- 开箱即用
- 联动逻辑实现高效
- 跨端能力,逻辑可跨框架,跨终端复用
- 动态渲染能力
3. formily 能解决什么问题
- 解决复杂场景,表单性能问题
- 可以实现复杂的表单联动逻辑
- 表单状态管理
- 表单场景化复用
- 后端也能搭建前端表单
4. 如何用 Formily 渲染一个登录表单
这里有一个场景,我们要实现如下效果:
正常来说需要用 Antd 等 UI 库写 Form 组件来实现,但是这里我们用 Formily 框架提供的能力来实现,接下来我们先了解一下 Formily 的渲染模式。
4.1 Formily 的渲染模式
Formily 支持 3 种渲染模式,JSX,Markup Schema 和 JSON Schema。我这里主要介绍后两种。
4.1.1 Markup Schema
Markup Schema 的使用方式,如下方代码所示,每个描述标签都代表一个 Schema 节点,与 JSON-Schema 等价,最终也会被编译为 JSON Schema。
<Form
form={normalForm}
layout="vertical"
size="large"
onAutoSubmit={console.log}
>
<SchemaField>
<SchemaField.String
name="username"
title="Username"
required
x-decorator="FormItem"
x-component="Input"
x-validator={{
required: true,
}}
x-component-props={{
prefix: "{{icon('UserOutlined')}}",
}}
/>
<SchemaField.String
name="password"
title="Password"
required
x-decorator="FormItem"
x-component="Password"
x-component-props={{
prefix: "{{icon('LockOutlined')}}",
}}
/>
</SchemaField>
Markup schema
是一个对源码开发比较友好的 Schema 开发模式,同样是使用 SchemaField
组件。
因为用 JSON Schema
在 JSX 环境下很难得到最好的智能提示体验,而且也不方便维护,用标签的形式可维护性会更好,智能提示也很强。
4.1.2 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')}}",
},
},
},
};
4.2 如何渲染 JSON Schema?
Formily 提供了 @formily/core
和 @formily/react
包,供我们以 JSON Schema 的形式渲染表单。
另外 Formily 也提供了自定义表单组件的能力。
import React from 'react';
import { createForm } from '@formily/core';
import { Form, FormItem, Input, Password, Submit } from '@formily/antd';
import { createSchemaField } from '@formily/react';
import * as ICONS from '@ant-design/icons';
const normalForm = createForm({
validateFirst: true,
});
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
Password,
},
scope: {
icon(name) {
return React.createElement(ICONS[name]);
},
},
});
// render it
const Basic = () => {
return (
<Form
form={normalForm}
layout="vertical"
size="large"
onAutoSubmit={console.log}
>
<SchemaField schema={normalSchema} />
<Submit block size="large">
Login
</Submit>
</Form>
)
}
优势:
- 后端也可以通过接口下发 JSON schema 来渲染前端表单,对于一个不了解前端技术的后端同学,也能够写表单。
- 同构的架构可以保证表单字段与服务端定义的一致,降低了前后端对齐接口协议的成本。
5. Formily Schema
属性 | 描述 | 类型 | 字段模型映射 |
---|---|---|---|
type | 类型 | SchemaTypes | GeneralField |
title | 标题 | React.ReactNode | title |
description | 描述 | React.ReactNode | description |
default | 默认值 | Any | initialValue |
readOnly | 是否只读 | Boolean | readOnly |
writeOnly | 是否只写 | Boolean | editable |
enum | 枚举 | SchemaEnum | dataSource |
const | 校验字段值是否与 const 的值相等 | Any | validator |
multipleOf | 校验字段值是否可被 multipleOf 的值整除 | Number | validator |
maximum | 校验最大值(大于) | Number | validator |
exclusiveMaximum | 校验最大值(大于等于 | Number | validator |
minimum | 校验最小值(小于) | Number | validator |
exclusiveMinimum | 最小值(小于等于) | Number | validator |
maxLength | 校验最大长度 | Number | validator |
minLength | 校验最小长度 | Number | validator |
pattern | 正则校验规则 | RegExpString | validator |
maxItems | 最大条目数 | Number | validator |
minItems | 最小条目数 | Number | validator |
uniqueItems | 是否校验重复 | Boolean | validator |
maxProperties | 最大属性数量 | Number | validator |
minProperties | 最小属性数量 | Number | validator |
required | 必填 | Boolean | validator |
format | 正则校验格式 | ValidatorFormats | validator |
properties | 属性描述 | SchemaProperties | – |
items | 数组描述 | SchemaItems | – |
additionalItems | 额外数组元素描述 | Schema | – |
patternProperties | 动态匹配对象的某个属性的 Schema | SchemaProperties | – |
additionalProperties | 匹配对象额外属性的 Schema | Schema | – |
x-index | UI 展示顺序 | Number | – |
x-pattern | UI 交互模式 | FieldPatternTypes | pattern |
x-display | UI 展示 | FieldDisplayTypes | display |
x-validator | 字段校验器 | FieldValidator | validator |
x-decorator | 字段 UI 包装器组件 | String | React.FC |
decorator |
x-decorator-props | 字段 UI 包装器组件属性 | Any | decorator |
x-component | 字段 UI 组件 | String | React.FC |
component |
x-component-props | 字段 UI 组件属性 | Any | component |
x-reactions | 字段联动协议 | SchemaReactions | reactions |
x-content | 字段内容,用来传入某个组件的子节点 | React.ReactNode | content |
x-visible | 字段显示隐藏 | Boolean | visible |
x-hidden | 字段 UI 隐藏(保留数据) | Boolean | hidden |
x-disabled | 字段禁用 | Boolean | disabled |
x-editable | 字段可编辑 | Boolean | editable |
x-read-only | 字段只读 | Boolean | readOnly |
x-read-pretty | 字段阅读态 | Boolean | readPretty |
definitions | Schema 预定义 | SchemaProperties | – |
$ref | 从 Schema 预定义中读取 Schema 并合并至当前 Schema | String | – |
x-data | 扩展属性 | Object | data |
x-compile-omitted | 忽略编译表达式的属性列表 | string[] | [] |
更多细节,可以参考 Formily API 文档
6. 表单联动展示
登录场景我们实现的是一个比较简单的 JSON Schema,仅仅描述了 2 个表单项。
现在有一个新的场景:
我们想实现当 showInput 为 show
时,展示 Input 表单项,hide
时,隐藏 Input 表单项,如下图所示:
这是一个一对一联动的 case。
在 Formily 中,联动分为主动联动和被动联动:
-
主动联动:只需要关注某个字段所依赖的字段即可,依赖字段变化了,被依赖的字段即可自动联动
-
被动联动:必须要监听一个或多个字段的事件变化去控制另一个或者多个字段的状态
6.1 主动联动
const schema = {
type: 'object',
properties: {
showInput: {
type: 'string',
title: 'showInput',
enum: [
{ label: 'show', value: 'visible' },
{ label: 'hide', value: 'none' },
],
default: 'visible',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': {
target: 'input',
fulfill: {
state: {
display: '{{$self.value}}',
},
},
},
},
input: {
type: 'string',
title: 'Input',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
};
6.2 被动联动
被动联动怎么写呢,这里举一个例子,实际上只有 x-reactions
参数的区别。在被动联动的表单项中,定义 dependencies
为依赖项:
const schema = {
type: 'object',
properties: {
showInput: {
type: 'string',
title: 'showInput',
enum: [
{ label: 'show', value: 'visible' },
{ label: 'hide', value: 'none' },
],
default: 'visible',
'x-decorator': 'FormItem',
'x-component': 'Select',
},
input: {
type: 'string',
title: 'Input',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-reactions': {
dependencies: ['showInput'],
fulfill: {
state: {
display: '{{$deps[0]}}',
},
},
},
},
},
};
一对多联动也是类似的,只是增加了一个表单项的区别。
另外,Formily 也支持链式联动,异步联动等联动方式。
7. 表单联动校验
如果我们想实现表单联动校验,要怎么做呢?
例如下图,我们想实现 AA 必须比 BB 大。
formily Schema 支持写 function 形式的 x-reactions
,在参数中我们可以拿到当前 form 实例挂载的其他字段的值,在函数中可以直接做判断。
const schema = {
type: 'object',
properties: {
aa: {
title: 'AA',
required: true,
'x-reactions': `{{(field) => {
field.selfErrors =
field.query('bb').value() >= field.value ? 'AA must be greater than BB' : ''
}}}`,
'x-component': 'NumberPicker',
'x-decorator': 'FormItem',
},
bb: {
title: 'BB',
required: true,
'x-reactions': {
dependencies: ['aa'],
fulfill: {
state: {
selfErrors:
"{{$deps[0] <= $self.value ? 'AA must be greater than BB' : ''}}",
},
},
},
'x-component': 'NumberPicker',
'x-decorator': 'FormItem',
},
},
};
8. 模型 inspector 工具
Formily 官方也提供了了 inspector 工具,可以查看 formily 表单每一个 Field 的内置状态,让你更好进行开发调试。
9. 拖拽开发
Formily 同样支持可视化拖拽表单开发,可以参考文档
10. 总结
表单场景一直都是前端中后台领域最复杂的场景。
Formily 是面向复杂场景的表单解决方案,提供了表单联动方案,可跨端,性能高,即便是后端,也能搭建前端表单。