业务场景中的表单开发,比较难实现的有两种:表单联动和可编辑表格。在antd中有shouldUpdate可以被动模型处理表单联动,有useWatch可以主动模型处理表单联动(联动模型概念);proComponents中EditableProTable组件可以快速实现可编辑表格。这两个方案是我没接触formily2.x之前经常使用的。
在使用了formily2.x后,这两个方案被抛弃了。第一是因为前面的两种方案都是以antd组件为基础实现的,且可编辑表格必须在react生态中使用,而formily2.x可以在vue或react或原生js中使用,并且官方已经支持antd、element、next、vant等UI框架,没有官方支持的UI框架也可以自己写一些桥接层去使用组件,使用方便。它专注于解决表单的方案,阿里团队开源,好用且坚挺,出来已经有3、4年了,它统共有三种开发方式,纯Jsx模式, Markup Schema模式和JSON Schema模式(三种模式介绍),下面的示例使用JSON Schema模式和antd5组件且主要使用被动模式,formilyjs中相关文档:Schema字段文档、antd5组件文档
schema常用字段名:
字段名 | 含义 |
---|---|
type | 类型 |
properties | 属性描述 |
x-decorator | 表单字段 UI 包装器组件(类似于antd中的FormItem,在使用中也可直接使用formily/antd-v5的 FormItem组件) |
x-decorator-props | 表单字段 UI 包装器组件上的属性,实际开发中没啥用,x-visible、x-hidden、x-dispaly、x-visible、title、required已经代替FormItem组件上的属性 |
x-validator | 字段校验器 |
title | 表单字段名标题 |
x-value | 初始值 |
x-visible | 控制表单字段显隐,隐藏时提交表单时不存在 |
x-hidden | 控制表单字段显隐,隐藏时提交表单时还是存在 |
x-dispaly | 没啥用,上面两个够用了 |
required | 透出标题的必填标识 |
x-component | 表单字段 UI 组件(createSchemaField引入哪个组件库就可以在内部使用) |
x-component-props | 表单字段 UI 组件上的属性(UI组件上有的属性的都可以用) |
x-reactions | 表单字段联动协议 |
$self | 当前字段自身实例 |
$deps | 只能在 x-reactions 中的表达式使用,与 x-reactions 定义的 dependencies 对应,能获取到依赖项相应的值 |
表单联动
通过dependencies做依赖收集,依赖更改时fulfill中条件满足则执行fulfill内schema中操作
js
import { FormButtonGroup, FormItem, Input, Submit } from '@formily/antd-v5';
import { createForm } from '@formily/core';
import { FormProvider, ISchema, createSchemaField } from '@formily/react';
import React from 'react';
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
}, //components组件注入后可以在schema中使用
});
const form = createForm(); //创建表单实例
const schema: ISchema = {
type: 'object',
properties: { //在下方注入当前需要的字段
input: {
type: 'string',
title: '输入框',
'x-decorator': 'FormItem',
'x-component': 'Input',
required: true,
'x-component-props': {
style: {
width: 240,
},
},
},
textarea: {
type: 'string',
title: '文本框',
required: true,
'x-reactions': {
dependencies: ['input'], //联动依赖项的字段
fulfill: { //fulfill有三种模式,实际上直接用schema即可,但有依赖项的字段更改,schema会被fulfill中schema替换
schema: {
'x-visible': "{{$deps[0] === '123'}}", //$deps为dependencies数组的字段值
//很多时候表单联动也只会跟其他表单项值的更改有关,直接使用这个是很方便的
'x-component-props.style.color':
"{{$self.value === '123' ? 'red' : 'blue'}}", //$self为本字段的实例,可以根据值做当前字段自身的修改
},
},
},
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
'x-component-props': {
style: {
width: 400,
},
},
},
},
};
const Demo: React.FC = () => {
return (
<FormProvider form={form}>
<SchemaField schema={schema} />
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
);
};
export default Demo;
自定义组件
类似于antd中form组件中编写自定义组件的操作,打印props可以看见onChange和value,通过onChange可以给value赋予新值实现自定义组件
js
import { FormButtonGroup, FormItem, Input, Submit } from '@formily/antd-v5';
import { createForm } from '@formily/core';
import { FormProvider, ISchema, createSchemaField } from '@formily/react';
import { Input as InputOfAntd } from 'antd';
import React from 'react';
const IncreaseOrDecrease = (props) => {
console.log(props,'props')
const changeNum = (type) => {
if (type === 'add') {
return props.onChange(Number(props.value) + 1);
}
props.onChange(Number(props.value) - 1);
};
return (
<div
style={{
display: 'flex',
}}
>
<div
onClick={() => {
changeNum('add');
}}
>
+
</div>
<InputOfAntd value={props.value} style={{ width: 400 }}></InputOfAntd>
<div
onClick={() => {
changeNum('reduce');
}}
>
-
</div>
</div>
);
};
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
IncreaseOrDecrease, //注入自定义组件
},
});
const form = createForm();
const schema: ISchema = {
type: 'object',
properties: {
input: {
type: 'number',
title: '增减',
required: true,
'x-value': 0, //初始值
'x-decorator': 'FormItem',
'x-component': 'IncreaseOrDecrease', //引入自定义组件
},
},
};
const Demo: React.FC = () => {
return (
<FormProvider form={form}>
<SchemaField schema={schema} />
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
);
};
export default Demo;
异步数据源
x-reactions中写函数获取异步数据源,scope可以注入其他函数给当前schema中使用,$self可以给当前字段使用
js
import { FormButtonGroup, FormItem, Select, Submit } from '@formily/antd-v5';
import { Field, createForm } from '@formily/core';
import { FormProvider, ISchema, createSchemaField } from '@formily/react';
import { action } from '@formily/reactive';
import React from 'react';
const SchemaField = createSchemaField({
components: {
Select,
FormItem,
},
});
const loadData = async (field: Field) => {
const value = field.value;
return fetch(
`https://api.oioweb.cn/api/search/taobao_suggest?keyword=${value}`,
)
.then((response) => response.json())
.then((res: any) => {
return res?.result.map((item) => {
return {
label: item[0],
value: item[1],
};
});
})
.catch(() => []);
};
const useAsyncDataSource = (service: typeof loadData) => (field: Field) => { //field即为当前字段实例
field.loading = true; //加上加载态
service(field).then(
//需要使用action.bound在异步更新数据后二次更新field实例(https://reactive.formilyjs.org/zh-CN/api/action)
action.bound?.((data) => {
field.dataSource = data;
field.loading = false;
}),
);
};
const onSearchValue = (searchValue, $self) => {
$self.value = searchValue; //给当前字段的值赋值
};
const form = createForm();
const schema: ISchema = {
type: 'object',
properties: {
select: {
type: 'string',
title: '异步选择框',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
showSearch: true,
filterOption: false,
onSearch: '{{(value)=>onSearchValue(value, $self)}}', //$self可以在字段描述中任意处使用
style: {
width: 120,
},
},
'x-reactions': ['{{useAsyncDataSource(loadData)}}'], //field即为当前字段实例
},
},
};
const Demo: React.FC = () => {
return (
<FormProvider form={form}>
<SchemaField
schema={schema}
scope={{ useAsyncDataSource, loadData, onSearchValue }} //注入方法
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
);
};
export default Demo;
可编辑表格
ArrayTable组件有ArrayTable.Column空节点作为表格列展示,ArrayTable.SortHandle提供拖拽能力,ArrayTable.Index提供当前行序号,ArrayTable.Addition新增一行表格,ArrayTable.Remove删除一行表格,ArrayTable.MoveDown下移一行表格,ArrayTable.MoveUp上移一行表格
js
import {
ArrayTable,
FormButtonGroup,
FormItem,
Input,
Submit,
Switch,
} from '@formily/antd-v5';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
FormItem,
Switch,
Input,
ArrayTable,
},
});
const form = createForm();
const schema = {
type: 'object',
properties: {
array: {
type: 'array',
'x-decorator': 'FormItem',
'x-component': 'ArrayTable',
'x-component-props': {
pagination: { pageSize: 10 },
scroll: { x: '100%' },
},
items: {
type: 'object',
properties: {
column1: {
type: 'void', //空节点(不会在表单中留下)
'x-component': 'ArrayTable.Column', //表格列
'x-component-props': { width: 50, title: 'Sort', align: 'center' },
properties: {
sort: {
type: 'void', //空节点(不会在表单中留下)
'x-component': 'ArrayTable.SortHandle', //提供表格可拖拽的能力
},
},
},
column2: {
type: 'void',
'x-component': 'ArrayTable.Column', //表格列
'x-component-props': { width: 80, title: 'Index', align: 'center' },
properties: {
index: {
type: 'void', /空节点(不会在表单中留下)
'x-component': 'ArrayTable.Index', //表格序号
},
},
},
column3: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { width: 100, title: '显隐->A2' },
properties: {
a1: {
type: 'boolean',
'x-decorator': 'FormItem',
'x-component': 'Switch',
},
},
},
column4: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { width: 200, title: 'A2' },
properties: {
a2: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
required: true,
'x-reactions': [
{
dependencies: ['.a1'], //当前显隐通过a1值控制
when: '{{$deps[0]}}',
fulfill: {
schema: {
'x-visible': false,
},
},
otherwise: {
schema: {
'x-visible': true,
},
},
},
],
},
},
},
column5: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { width: 200, title: 'A3' },
properties: {
a3: { //当前可编辑表格中a3字段
type: 'string',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
column6: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': {
title: 'Operations',
dataIndex: 'operations',
width: 200,
fixed: 'right',
},
properties: {
item: {
type: 'void',
'x-component': 'FormItem',
properties: {
remove: {
type: 'void', // 空节点,只操作不影响表单值
'x-component': 'ArrayTable.Remove', //删除
},
moveDown: {
type: 'void',
'x-component': 'ArrayTable.MoveDown', //上移
},
moveUp: {
type: 'void',
'x-component': 'ArrayTable.MoveUp', //下移
},
},
},
},
},
},
},
properties: {
add: {
type: 'void',
'x-component': 'ArrayTable.Addition', //下方的新增可编辑内容按钮
title: '添加条目',
},
},
},
},
};
export default () => {
return (
<FormProvider form={form}>
<SchemaField schema={schema} />
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
);
};