0 概述
Formily的Antd库经验汇总,这个库在学习了Reactive,Core与React库以后就相当简单了,
在使用Formily的Antd库的时候,要转换一下思维。React是输入不同的数据,每次重新render。而使用Formily的时候,是首次渲染固定的Schema,与固定的数据结构。用户触发的时候不能修改Schema,也不能修改数据结构,开发者只能修改数据本身。这种开发方式其实与WPF开发比较相似。这是一种约束,也是表单这种特定场景下更好的开发方式。
1 基础组件
代码在这里
1.1 输入组件
import React from 'react';
import {
Input,
FormItem,
FormButtonGroup,
Submit,
NumberPicker,
Password,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField, FormConsumer } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
NumberPicker,
Password,
},
});
const form = createForm();
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="input box"
x-decorator="FormItem"
x-component="Input"
required
x-component-props={{
style: {
width: 240,
},
}}
/>
<SchemaField.String
name="input2"
title="text box"
x-decorator="FormItem"
required
x-component="Input.TextArea"
x-component-props={{
style: {
width: 400,
},
}}
/>
<SchemaField.String
name="input3"
title="input box"
x-decorator="FormItem"
x-component="NumberPicker"
required
x-component-props={{
style: {
width: 240,
},
}}
/>
<SchemaField.String
name="input4"
title="input box"
x-decorator="FormItem"
x-component="Password"
required
x-component-props={{
checkStrength: true,
}}
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
<FormConsumer>
{(form) => {
return <div>{JSON.stringify(form.values)}</div>;
}}
</FormConsumer>
</FormProvider>
);
这个也没啥好说的
1.2 多选与单选组件
import React from 'react';
import {
Checkbox,
FormItem,
FormButtonGroup,
Submit,
Radio,
Switch,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField, FormConsumer } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Checkbox,
Radio,
FormItem,
Switch,
},
});
const form = createForm();
export default () => {
//注意CheckBox与CheckBox.Group的区别,可选的数值写入enum
return (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Boolean
name="input"
title="Are you sure"
x-decorator="FormItem"
x-component="Checkbox"
/>
<SchemaField.Number
name="input1_2"
title="Switch"
x-decorator="FormItem"
x-component="Switch"
/>
<SchemaField.String
name="input2"
title="Check"
enum={[
{
label: 'Option 1',
value: 1,
},
{
label: 'Option 2',
value: 2,
},
]}
x-decorator="FormItem"
x-component="Checkbox.Group"
/>
<SchemaField.Number
name="input3"
title="single choice"
enum={[
{
label: 'Option 1',
value: 1,
},
{
label: 'Option 2',
value: 2,
},
]}
x-decorator="FormItem"
x-component="Radio.Group"
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
<FormConsumer>
{(form) => {
return <div>{JSON.stringify(form.values)}</div>;
}}
</FormConsumer>
</FormProvider>
);
};
注意CheckBox与Switch是类似的,而CheckBox.Group与Radio.Group是类似的
1.3 选择组件
import React from 'react';
import {
Select,
FormItem,
FormButtonGroup,
Submit,
TreeSelect,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField, FormConsumer } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Select,
TreeSelect,
FormItem,
},
});
const form = createForm();
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Number
name="input"
title="select box"
x-decorator="FormItem"
x-component="Select"
enum={[
{ label: 'Option 1', value: 1 },
{ label: 'Option 2', value: 2 },
]}
x-component-props={{
style: {
width: 120,
},
}}
/>
<SchemaField.Number
name="input2"
title="select box"
x-decorator="FormItem"
x-component="TreeSelect"
enum={[
{
label: 'Option 1',
value: 1,
children: [
{
title: 'Child Node1',
value: '0-0-0',
key: '0-0-0',
},
{
title: 'Child Node2',
value: '0-0-1',
key: '0-0-1',
},
{
title: 'Child Node3',
value: '0-0-2',
key: '0-0-2',
},
],
},
{
label: 'Option 2',
value: 2,
children: [
{
title: 'Child Node3',
value: '0-1-0',
key: '0-1-0',
},
{
title: 'Child Node4',
value: '0-1-1',
key: '0-1-1',
},
{
title: 'Child Node5',
value: '0-1-2',
key: '0-1-2',
},
],
},
]}
x-component-props={{
style: {
width: 200,
},
}}
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
<FormConsumer>
{(form) => {
return <div>{JSON.stringify(form.values)}</div>;
}}
</FormConsumer>
</FormProvider>
);
选择组件主要是Select与TreeSelect的两种
1.4 日期与时间组件
import React from 'react';
import {
TimePicker,
FormItem,
FormButtonGroup,
Submit,
DatePicker,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField, FormConsumer } from '@formily/react';
const SchemaField = createSchemaField({
components: {
TimePicker,
FormItem,
DatePicker,
},
});
const form = createForm();
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input1"
title="time"
required
x-decorator="FormItem"
x-component="TimePicker"
/>
<SchemaField.String
name="[input2,input3]"
title="time range"
x-decorator="FormItem"
x-component="TimePicker.RangePicker"
/>
<SchemaField.String
name="input4"
required
title="normal date"
x-decorator="FormItem"
x-component="DatePicker"
/>
<SchemaField.String
name="input5"
title="Week Selection"
x-decorator="FormItem"
x-component="DatePicker"
x-component-props={{
picker: 'week',
}}
/>
<SchemaField.String
name="[input6,input7]"
required
title="normal date"
x-decorator="FormItem"
x-component="DatePicker.RangePicker"
/>
<SchemaField.String
name="[input7,input8]"
title="Week Selection"
x-decorator="FormItem"
x-component="DatePicker.RangePicker"
x-component-props={{
picker: 'week',
}}
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
<FormConsumer>
{(form) => {
return <div>{JSON.stringify(form.values)}</div>;
}}
</FormConsumer>
</FormProvider>
);
日期与时间组件,这个Formily的API真的好好用
2 表单展示形式
代码在这里
2.1 对话框,FormDialog
import React from 'react';
import { FormDialog, FormItem, FormLayout, Input, Space } from '@formily/antd';
import { createSchemaField } from '@formily/react';
import { Button } from 'antd';
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
},
});
export default () => {
return (
<Space>
<Button
onClick={async () => {
//命令方式的Modal表单
//Modal里面自带一个FormProvider
//FormDialog.Footer是额外插入的dom
let dialog = FormDialog('弹窗表单', () => {
return (
<FormLayout labelCol={6} wrapperCol={10}>
<SchemaField>
<SchemaField.String
name="aaa"
required
title="输入框1"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="bbb"
required
title="输入框2"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="ccc"
required
title="输入框3"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="ddd"
required
title="输入框4"
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormDialog.Footer>
<span
style={{
//看这里https://github.com/alibaba/formily/blob/formily_next/packages/antd/src/form-dialog/index.tsx
marginLeft: 4, //Footer的实现也很巧妙,它在FormProvider里面创造了一个div,并绑定了一个ref
//然后在useLayoutEffect里面,根据div所在位置找到了form的dom,然后form的dom找到了footer的位置
//最后在footer的位置重新render自己
}}
>
扩展文案
</span>
</FormDialog.Footer>
</FormLayout>
);
});
//open是一个Promise的返回值,它实际是表单的values值返回了
let result = await dialog.open({
initialValues: {
aaa: '123',
},
});
console.log(result);
}}
>
点我打开表单
</Button>
<Button
onClick={async () => {
//表单的第一个参数可以是Modal类型的对象
let dialog = FormDialog(
{
title: '弹窗表单',
okText: '确认',
cancelText: '取消',
},
//不要尝试自己用FormProvider,这是没用的,因为在open以后它指定了从它自己创造form里面拉数据
() => {
return (
<FormLayout labelCol={6} wrapperCol={10}>
<SchemaField>
<SchemaField.String
name="aaa"
required
title="输入框1"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="bbb"
required
title="输入框2"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="ccc"
required
title="输入框3"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="ddd"
required
title="输入框4"
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormDialog.Footer>
<span style={{ marginLeft: 4 }}>
扩展文案
</span>
</FormDialog.Footer>
</FormLayout>
);
},
);
let result = await dialog.open({
initialValues: {
aaa: '123',
},
});
console.log(result);
}}
>
点我打开表单2
</Button>
</Space>
);
};
命令方式的对话框表单,内容自带了一个FormProvider
2.2 浮层,FormDrawer
import React from 'react';
import {
FormDrawer,
FormItem,
FormLayout,
Input,
Submit,
Reset,
FormButtonGroup,
} from '@formily/antd';
import { createSchemaField } from '@formily/react';
import { Button } from 'antd';
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
},
});
export default () => {
return (
<Button
onClick={async () => {
//FormDrawer的用法与FormDialog的用法是一样的,这里也没啥好说的了
let drawer = FormDrawer('抽屉表单', (resolve) => {
return (
<FormLayout labelCol={6} wrapperCol={10}>
<SchemaField>
<SchemaField.String
name="aaa"
required
title="输入框1"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="bbb"
required
title="输入框2"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="ccc"
required
title="输入框3"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="ddd"
required
title="输入框4"
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormDrawer.Footer>
<FormButtonGroup align="right">
<Submit onClick={resolve}>提交</Submit>
<Reset>重置</Reset>
</FormButtonGroup>
</FormDrawer.Footer>
</FormLayout>
);
});
let result = await drawer.open({
initialValues: {
aaa: '123',
},
});
console.log(result);
}}
>
点我打开表单
</Button>
);
};
浮层与对话框的方式几乎一样,也没啥好说的
2.3 分步表单,FormStep
import React from 'react';
import { FormStep, FormItem, Input, FormButtonGroup } from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, FormConsumer, createSchemaField } from '@formily/react';
import { Button } from 'antd';
const SchemaField = createSchemaField({
components: {
FormItem,
FormStep,
Input,
},
});
const form = createForm();
//类似FormStep的实现
/*
返回值是FormStep
export interface IFormStep {
connect: (steps: SchemaStep[], field: VoidField) => void;
current: number;
allowNext: boolean;
allowBack: boolean;
setCurrent(key: number): void;
submit: Form['submit'];
next(): void;
back(): void;
}
*/
const formStep = FormStep.createFormStep();
//这种方法用得比较多
export default () => {
return (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Void
x-component="FormStep" //顶部的容器组件
x-component-props={{
formStep, //绑定FormStep的Model数据
}}
>
<SchemaField.Void
x-component="FormStep.StepPane" //下一级的Pane组件
x-component-props={{
title: '第一步', //标题
}}
>
<SchemaField.String
name="aaa"
x-decorator="FormItem"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
x-component="FormStep.StepPane"
x-component-props={{ title: '第二步' }}
>
<SchemaField.String
name="bbb"
x-decorator="FormItem"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
x-component="FormStep.StepPane"
x-component-props={{ title: '第三步' }}
>
<SchemaField.String
name="ccc"
x-decorator="FormItem"
required
x-component="Input"
/>
</SchemaField.Void>
</SchemaField.Void>
</SchemaField>
<FormConsumer>
{() => (
//将整个组件放在FormConsumer,这里沿用了React的render做法,每次表单变化,都出现render
//这是一种做法
<FormButtonGroup>
<Button
disabled={!formStep.allowBack}
onClick={() => {
//返回是不需要对表单进行validate的
formStep.back();
}}
>
上一步
</Button>
<Button
disabled={!formStep.allowNext}
onClick={() => {
//每次进行next的时候,FormStep内部先对表单进行validate
formStep.next();
}}
>
下一步
</Button>
<Button
disabled={formStep.allowNext}
onClick={() => {
formStep.submit(console.log);
}}
>
提交
</Button>
</FormButtonGroup>
)}
</FormConsumer>
</FormProvider>
);
};
分步表单重要,经常会用到
2.4 标签页,FormTab
import React from 'react';
import {
FormTab,
FormItem,
Input,
FormButtonGroup,
Submit,
Form,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField, FormConsumer } from '@formily/react';
import { Button } from 'antd';
/*
FormTab的源代码
//FormTab本身就是一个model
const createFormTab = (defaultActiveKey?: string) => {
const formTab = model({
activeKey: defaultActiveKey,
setActiveKey(key: string) {
formTab.activeKey = key
},
})
return formTab
}
//初始化的时候用传入的formTab,或者自己创建一个FromTab
const _formTab = useMemo(() => {
return formTab ? props.formTab : createFormTab()
}, [])
const activeKey = props.activeKey || _formTab?.activeKey
//在Tabs的onChange的时候,更改activeKey,注意Tabs控件是受控组件
<Tabs
{...props}
className={cls(prefixCls, props.className)}
activeKey={activeKey}
onChange={(key) => {
props.onChange?.(key)
formTab?.setActiveKey?.(key)
}}
>
xxx
</Tabs>
*/
const SchemaField = createSchemaField({
components: {
FormItem,
FormTab,
Input,
},
});
const form = createForm();
//获取FormTab特定的ViewModel,可以控制FormTab的激活的位置
/*
返回值为IFormTab,一个observable对象
export interface IFormTab {
activeKey: string;
setActiveKey(key: string): void;
}
*/
const formTab = FormTab.createFormTab();
//这个示例其实很少用,将多个Tab聚合为一个表单来提交,这样是不合理的
export default () => {
return (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Void
x-component="FormTab" //顶部Tab
x-component-props={{
formTab, //这里要传入FormTab的model
}}
>
<SchemaField.Void
name="tab1" //这里作为tab的key
x-component="FormTab.TabPane"
x-component-props={{
tab: '标签1', //这里填写标签的名称
}}
>
<SchemaField.String
name="aaa"
x-decorator="FormItem"
title="AAA"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
name="tab2"
x-component="FormTab.TabPane"
x-component-props={{ tab: '标签2' }}
>
<SchemaField.String
name="bbb"
x-decorator="FormItem"
title="BBB"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
name="tab3" //active Key
x-component="FormTab.TabPane"
x-component-props={{ tab: '标签3' }}
>
<SchemaField.String
name="ccc"
x-decorator="FormItem"
title="CCC"
required
x-component="Input"
/>
</SchemaField.Void>
</SchemaField.Void>
</SchemaField>
<FormButtonGroup.FormItem>
<Button
onClick={() => {
form.query('tab3').take((field) => {
field.visible = !field.visible;
});
}}
>
显示/隐藏最后一个Tab
</Button>
<Button
onClick={() => {
formTab.setActiveKey('tab2');
}}
>
切换第二个Tab
</Button>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup.FormItem>
<FormConsumer>
{(form) => {
return <div>{JSON.stringify(form.values)}</div>;
}}
</FormConsumer>
</FormProvider>
);
};
标签页表单的方式其实很少用,多个标签都是共享一个表单内容的
2.5 可折叠面板,FormCollapse
import React from 'react';
import {
FormCollapse,
FormLayout,
FormItem,
Input,
FormButtonGroup,
Submit,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField, FormConsumer } from '@formily/react';
import { Button } from 'antd';
const SchemaField = createSchemaField({
components: {
FormItem,
FormCollapse,
Input,
},
});
const form = createForm();
/*
//FormCollapse与FormTab不同的是,它可以同时打开多个折叠面板,不像Tab每次只能打开一个面板
返回值是IFormCollapse类型
export interface IFormCollapse {
activeKeys: ActiveKeys;
hasActiveKey(key: ActiveKey): boolean;
setActiveKeys(key: ActiveKeys): void;
addActiveKey(key: ActiveKey): void;
removeActiveKey(key: ActiveKey): void;
toggleActiveKey(key: ActiveKey): void;
}
*/
const formCollapse = FormCollapse.createFormCollapse();
//这个组件偶尔也能用一下
export default () => {
return (
<FormProvider form={form}>
<FormLayout labelCol={6} wrapperCol={10}>
<SchemaField>
<SchemaField.Void
title="折叠面板"
x-decorator="FormItem"
x-component="FormCollapse" //容器组件
x-component-props={{
formCollapse, //绑定FormCollapse的Model数据
}}
>
<SchemaField.Void
name="panel1" //面板key
x-component="FormCollapse.CollapsePanel" //容器的面板数据
x-component-props={{
header: '面板1', //标题数据
}}
>
<SchemaField.String
name="aaa"
title="AAA"
x-decorator="FormItem"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
name="panel2"
x-component="FormCollapse.CollapsePanel"
x-component-props={{ header: '面板2' }}
>
<SchemaField.String
name="bbb"
title="BBB"
x-decorator="FormItem"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
name="panel3"
x-component="FormCollapse.CollapsePanel"
x-component-props={{ header: '面板3' }}
>
<SchemaField.String
name="ccc"
title="CCC"
x-decorator="FormItem"
required
x-component="Input"
/>
</SchemaField.Void>
</SchemaField.Void>
</SchemaField>
<FormButtonGroup.FormItem>
<Button
onClick={() => {
form.query('panel3').take((field) => {
field.visible = !field.visible;
});
}}
>
显示/隐藏最后一个Tab
</Button>
<Button
onClick={() => {
formCollapse.toggleActiveKey('panel2');
}}
>
切换第二个Tab
</Button>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup.FormItem>
<FormConsumer>
{(form) => {
return <div>{JSON.stringify(form.values)}</div>;
}}
</FormConsumer>
</FormLayout>
</FormProvider>
);
};
可折叠面板与标签页表单形式也很少用,建议不要用
3 表单布局,FormLayout
代码在这里
3.1 字段包围组件,FormItem
import React from 'react';
import {
Input,
Select,
FormItem,
FormButtonGroup,
Submit,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
},
});
const form = createForm();
export default () => {
let description = `
FormLayout是用来控制所有FormItem的默认样式的
那么FormItem就是单独的FormItem的独立样式的
`;
return (
<div style={{ padding: '30px' }}>
<FormProvider form={form}>
<pre>{description}</pre>
<SchemaField>
<SchemaField.String
name="input"
title="输入框"
x-decorator="FormItem"
x-component="Input"
required
/>
<SchemaField.String
name="input2" //空label,title设置为空
x-decorator="FormItem"
x-component="Input"
required
/>
<SchemaField.String
name="input3" //没有冒号,用colon
title="输入框"
x-decorator="FormItem"
x-decorator-props={{ colon: false }}
x-component="Input"
required
/>
<SchemaField.String
name="input4" //问号的提示信息,用tooltip
title="输入框"
x-decorator="FormItem"
x-decorator-props={{ tooltip: <div>123</div> }}
x-component="Input"
required
/>
<SchemaField.String
name="input4_prefix" //前缀,用addonBefore
title="输入框"
x-decorator="FormItem"
x-decorator-props={{ addonBefore: <div>prefix</div> }}
x-component="Input"
/>
<SchemaField.String
name="inpt4_suffix" //后缀,用addonAfter
title="输入框"
x-decorator="FormItem"
x-decorator-props={{ addonAfter: <div>suffix</div> }}
x-component="Input"
/>
<SchemaField.String
name="input5" //没有required,但是需要用*号,用asterisk
title="输入框"
x-decorator="FormItem"
x-decorator-props={{ asterisk: true }}
x-component="Input"
/>
<SchemaField.String
name="input6" //额外描述,用extra
title="输入框"
x-decorator="FormItem"
x-decorator-props={{ extra: '额外描述' }}
x-component="Input"
/>
<SchemaField.String
name="input7" //无边框,用bordered
title="输入框"
x-decorator="FormItem"
x-decorator-props={{ bordered: false }}
x-component="Input"
/>
<SchemaField.String
name="input8" //全包围边框,用inset
title="输入框"
x-decorator="FormItem"
x-decorator-props={{ inset: true }}
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
</div>
);
};
FormItem的样式设置还是非常多,要熟悉一下
3.2 统一字段包围组件,FormLayout
3.2.1 基础
import React from 'react';
import { Input, Select, FormItem, FormLayout } from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
FormLayout,
},
});
const form = createForm();
export default () => {
let description = `
FormLayout是用来控制所有FormItem的默认样式的
默认是有冒号,horizontal的
`;
return (
<FormProvider form={form}>
<pre>{description}</pre>
<SchemaField>
<SchemaField.Void
x-component="FormLayout"
x-component-props={{}}
>
<SchemaField.String
name="input"
title="输入"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
<SchemaField.String
name="select"
title="选择框"
x-decorator="FormItem"
x-component="Select"
required
/>
</SchemaField.Void>
</SchemaField>
</FormProvider>
);
};
FormLayout组件,就是统一设置FormItem组件的组件而已
3.2.2 feedbackLayout
import React from 'react';
import { Input, Select, FormItem, FormLayout } from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
FormLayout,
},
});
const form = createForm();
export default () => {
let description = `
FormLayout是用来控制所有FormItem的默认样式的
feedbackLayout就是校验失败时,怎样提示:
* loose,默认值,UI默认留有失败文案的位置
* terse,UI默认没有失败文案的位置,失败的时候再腾出位置
* popover,文案提示是弹出方式的
* none,没有文案提示
`;
return (
<FormProvider form={form}>
<pre>{description}</pre>
<SchemaField>
<SchemaField.Void
x-component="FormLayout"
x-component-props={{
feedbackLayout: 'terse',
}}
>
<SchemaField.String
name="input"
title="输入"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
<SchemaField.String
name="select"
title="选择框"
x-decorator="FormItem"
x-component="Select"
required
/>
</SchemaField.Void>
</SchemaField>
</FormProvider>
);
};
feedbackLayout经常用,它用来控制校验失败时,对用户的反馈方式
3.2.3 layout
import React from 'react';
import { Input, Select, FormItem, FormLayout } from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
FormLayout,
},
});
const form = createForm();
export default () => {
let description = `
FormLayout是用来控制所有FormItem的默认样式的
layout有vertical,horizontal,和inline
inline会紧紧地放在一起
layout是指label与wrapper的排版
`;
return (
<FormProvider form={form}>
<pre>{description}</pre>
<SchemaField>
<SchemaField.Void
x-component="FormLayout"
x-component-props={{
layout: 'vertical',
}}
>
<SchemaField.String
name="input"
title="输入"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
<SchemaField.String
name="select"
title="选择框"
x-decorator="FormItem"
x-component="Select"
required
/>
</SchemaField.Void>
</SchemaField>
</FormProvider>
);
};
FormLayout的layout是指label与wrapper之间的排版方式
3.2.4 size
import React from 'react';
import { Input, Select, FormItem, FormLayout } from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
FormLayout,
},
});
const form = createForm();
export default () => {
let description = `
FormLayout是用来控制所有FormItem的默认样式的
size有large,small,default
`;
return (
<FormProvider form={form}>
<pre>{description}</pre>
<SchemaField>
<SchemaField.Void
x-component="FormLayout"
x-component-props={{
size: 'small',
}}
>
<SchemaField.String
name="input"
title="输入"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
<SchemaField.String
name="select"
title="选择框"
x-decorator="FormItem"
x-component="Select"
required
/>
</SchemaField.Void>
</SchemaField>
</FormProvider>
);
};
控制FormItem的大小
3.2.5 labelCol与wrapperCol
import React from 'react';
import { Input, Select, FormItem, FormLayout } from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
FormLayout,
},
});
const form = createForm();
export default () => {
let description = `
FormLayout是用来控制所有FormItem的默认样式的
labelCol与wrapperCol是用栅栏布局,来确定label与wrapper的长度
栅栏布局的总长度是24
`;
return (
<FormProvider form={form}>
<pre>{description}</pre>
<SchemaField>
<SchemaField.Void
x-component="FormLayout"
x-component-props={{
labelCol: 6,
wrapperCol: 10,
}}
>
<SchemaField.String
name="input"
title="输入"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
<SchemaField.String
name="select"
title="选择框"
x-decorator="FormItem"
x-component="Select"
required
/>
</SchemaField.Void>
</SchemaField>
</FormProvider>
);
};
labelCol与wrapperCol占用的列,每行总列数为24
3.2.6 labelAlign与wrapperAlign
import React from 'react';
import { Input, Select, FormItem, FormLayout } from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
FormLayout,
},
});
const form = createForm();
export default () => {
let description = `
FormLayout是用来控制所有FormItem的默认样式的
labelAlign,默认为right
wrapperAlign,默认为left
`;
return (
<FormProvider form={form}>
<pre>{description}</pre>
<SchemaField>
<SchemaField.Void
x-component="FormLayout"
x-component-props={{
labelCol: 6,
wrapperWidth: 300,
labelAlign: 'left',
wrapperAlign: 'right',
}}
>
<SchemaField.String
name="input"
title="输入"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
<SchemaField.String
name="select"
title="选择框"
x-decorator="FormItem"
x-component="Select"
required
/>
</SchemaField.Void>
</SchemaField>
</FormProvider>
);
};
label与wrapper的对齐方式
3.3 表单组件,Form
import React from 'react';
import { Input, Select, FormItem, FormLayout, Form } from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
FormLayout,
},
});
const form = createForm();
export default () => {
let description = `
Form其实是FormProvider,与FormLayout的组合
并且提供了onAutoSubmit的方法
`;
return (
<Form form={form} layout={'vertical'} onAutoSubmit={console.log}>
<pre>{description}</pre>
<SchemaField>
<SchemaField.Void
x-component="FormLayout"
x-component-props={{}}
>
<SchemaField.String
name="input"
title="输入"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
<SchemaField.String
name="select"
title="选择框"
x-decorator="FormItem"
x-component="Select"
required
/>
</SchemaField.Void>
</SchemaField>
</Form>
);
};
Form组件其实就是FormProvider,与FormLayout的组合而已,它拥有了FormLayout的所有属性
3.4 表单栅栏布局组件,FormGrid
FormLayout就是按照列的方式为每个字段布局
3.4.1 基础
import React from 'react';
import {
Input,
Select,
FormItem,
FormLayout,
Form,
FormGrid,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
FormGrid,
},
});
const form = createForm();
export default () => {
let description = `
FormGrid通过限制maxColumns来控制每行有多少个组件
`;
return (
<div style={{ margin: '30px', border: '1px solid black' }}>
<Form form={form} labelWidth={100}>
<pre>{description}</pre>
<SchemaField>
<SchemaField.Void
x-component="FormGrid"
x-component-props={{
maxColumns: 2,
}}
>
<SchemaField.String
name="input"
title="输入框"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
<SchemaField.String
name="input2"
title="输入框2"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
<SchemaField.String
name="input3"
title="输入框3"
x-decorator="FormItem"
x-decorator-props={{
gridSpan: 2,
}}
x-component="Input"
required
/>
</SchemaField.Void>
</SchemaField>
</Form>
</div>
);
};
FormGrid与FormItem的gridSpan配合使用,可以使得某些组件可以占用2列,普通的组件只能用1列。
3.4.2 响应式
import React from 'react';
import {
Input,
Select,
FormItem,
FormLayout,
Form,
FormGrid,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
FormGrid,
},
});
const form = createForm();
export default () => {
let description = `
FormGrid通过限制maxColumns来控制每行有多少个组件
maxColumns可以是一个数组
默认为[720,1280,1920]的容器不同宽度下的分界方法
方便实现响应式布局
`;
return (
<div style={{ margin: '30px', border: '1px solid black' }}>
<Form form={form} layout="vertical" feedbackLayout="none">
<pre>{description}</pre>
<SchemaField>
<SchemaField.Void
x-component="FormGrid"
x-component-props={{
maxColumns: [1, 3, 5],
}}
>
<SchemaField.String
name="input"
title="输入框"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
<SchemaField.String
name="input2"
title="输入框2"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
<SchemaField.String
name="input3"
title="输入框3"
x-decorator="FormItem"
x-decorator-props={{
gridSpan: 2,
}}
x-component="Input"
required
/>
<SchemaField.String
name="input4"
title="输入框4"
x-decorator="FormItem"
x-decorator-props={{
gridSpan: 3,
}}
x-component="Input"
required
/>
<SchemaField.String
name="input5"
title="输入框5"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
<SchemaField.String
name="input6"
title="输入框6"
x-decorator="FormItem"
x-decorator-props={{}}
x-component="Input"
required
/>
</SchemaField.Void>
</SchemaField>
</Form>
</div>
);
};
FormGrid的maxColumns是一个数组,可以设置在不同屏幕宽度下分的列数,以实现响应式布局
3.5 表单按钮群组件,FormButtonGroup
import React from 'react';
import {
FormButtonGroup,
Submit,
Reset,
FormItem,
Input,
FormLayout,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
},
});
const form = createForm();
export default () => {
//FormButtonGroup.FormItem能够用来对齐FormLayout,与其他字段的wrapperCol对齐。它其实就是个空Label的FormItem而已
//Sticky可以做到长表单的时候,底部的按钮群像fixed一样保持不动,不会因为上下滚动而变动位置。搭配FormButtonGroup可以设置好是在left,right还是center
return (
<FormProvider form={form}>
<FormLayout labelCol={6} wrapperCol={10}>
<SchemaField>
<SchemaField.String
title="输入框"
x-decorator="FormItem"
required
x-component="Input"
/>
<SchemaField.String
title="输入框"
x-decorator="FormItem"
required
x-component="Input"
/>
<SchemaField.String
title="输入框"
x-decorator="FormItem"
required
x-component="Input"
/>
</SchemaField>
<FormButtonGroup.FormItem>
<Submit onSubmit={console.log}>提交</Submit>
<Reset>重置</Reset>
</FormButtonGroup.FormItem>
</FormLayout>
<FormLayout labelCol={6} wrapperCol={10}>
<SchemaField>
<SchemaField.String
title="输入框"
x-decorator="FormItem"
required
x-component="Input"
/>
<SchemaField.String
title="输入框"
x-decorator="FormItem"
required
x-component="Input"
/>
<SchemaField.String
title="输入框"
x-decorator="FormItem"
required
x-component="Input"
/>
</SchemaField>
<FormButtonGroup.Sticky align="center">
<FormButtonGroup gutter={10}>
<Submit onSubmit={console.log}>提交</Submit>
<Reset>重置</Reset>
</FormButtonGroup>
</FormButtonGroup.Sticky>
</FormLayout>
</FormProvider>
);
};
FormButtonGroup的业务特点是,按钮群需要有间隔,同时需要Sticky,或者对齐FormItem
3.6 通用Flex组件,Space
Space组件其实就是flex布局加上Gap而已
3.6.1 基础
import React from 'react';
import {
Input,
Select,
FormItem,
FormButtonGroup,
Submit,
Space,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
Space,
},
});
const form = createForm();
export default () => {
let description = `
Space其实就是一个包围组件,它默认就是flex布局,可以控制包围组件下各个元素的间隙
* 而Space组件一般用FormItem组件组合一起使用。本来FormItem自身就有feedbackLayout的空隙,再加上Input组件的FormItem也有feedbackLayout的空隙,这样就会产生双重空隙
因此,总是要将Space组件的FormItem的空隙设置为空
* 另外一方面,每个Input都属于FormItem,来显示校验错误时的错误描述,所以,Input下的FormItem是不能省略的。
`;
return (
<div style={{ padding: '30px' }}>
<FormProvider form={form}>
<pre>{description}</pre>
<SchemaField>
<SchemaField.Void
title="name"
x-decorator="FormItem"
x-decorator-props={{
asterisk: true,
feedbackLayout: 'none',
}}
x-component="Space"
>
<SchemaField.String
name="firstName"
x-decorator="FormItem"
x-component="Input"
required
/>
<SchemaField.String
name="lastName"
x-decorator="FormItem"
x-component="Input"
required
/>
</SchemaField.Void>
<SchemaField.Void
title="Text concatenation"
x-decorator="FormItem"
x-decorator-props={{
asterisk: true,
feedbackLayout: 'none',
}}
x-component="Space"
>
<SchemaField.String
name="aa"
x-decorator="FormItem"
x-component="Input"
x-decorator-props={{
addonAfter: 'Unit',
}}
required
/>
<SchemaField.String
name="bb"
x-decorator="FormItem"
x-component="Input"
x-decorator-props={{
addonAfter: 'Unit',
}}
required
/>
<SchemaField.String
name="cc"
x-decorator="FormItem"
x-component="Input"
x-decorator-props={{
addonAfter: 'Unit',
}}
required
/>
</SchemaField.Void>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
</div>
);
};
Space组件可以实现更为灵活的表单布局
3.6.2 方向
import React from 'react';
import {
Input,
Select,
FormItem,
FormButtonGroup,
Submit,
Form,
Space,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { Button } from 'antd';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
Space,
Button,
},
});
const form = createForm();
export default () => {
let description = `
Space组件默认设置自身为inline-flex,就是自身为inline组件
然后设置direction为vertical,那么每个子组件都是垂直摆放的
`;
return (
<div style={{ padding: '30px' }}>
<Form form={form} feedbackLayout="none">
<pre>{description}</pre>
<SchemaField>
<SchemaField.Void
x-component="Space"
x-component-props={{
direction: 'vertical',
}}
>
<SchemaField.String
name="input1"
title="输入框1"
x-decorator="FormItem"
x-component="Input"
x-component-props={{}}
required
/>
<SchemaField.String
name="input2"
title="输入框2"
x-decorator="FormItem"
x-component="Input"
x-component-props={{}}
required
/>
<SchemaField.String
name="input3"
title="输入框3"
x-decorator="FormItem"
x-component="Input"
x-component-props={{}}
required
/>
</SchemaField.Void>
<SchemaField.Void
x-component="Space"
x-component-props={{
direction: 'vertical',
}}
>
<SchemaField.String
name="input4"
title="输入框4"
x-decorator="FormItem"
x-component="Input"
x-component-props={{}}
required
/>
<SchemaField.String
name="input5"
title="输入框5"
x-decorator="FormItem"
x-component="Input"
x-component-props={{}}
required
/>
<SchemaField.String
name="input6"
title="输入框6"
x-decorator="FormItem"
x-component="Input"
x-component-props={{}}
required
/>
</SchemaField.Void>
</SchemaField>
</Form>
</div>
);
};
Space组件设置子组件的排列方向
3.6.3 环绕与间隔
import React from 'react';
import {
Input,
Select,
FormItem,
FormButtonGroup,
Submit,
Form,
Space,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { Button } from 'antd';
import { FormProvider, createSchemaField } from '@formily/react';
const SchemaField = createSchemaField({
components: {
Input,
Select,
FormItem,
Space,
Button,
},
});
const form = createForm();
export default () => {
let description = `
Space组件可以设置环绕的方式存放
`;
return (
<div style={{ padding: '30px' }}>
<Form form={form} feedbackLayout="none">
<pre>{description}</pre>
<SchemaField>
<SchemaField.Void
x-component="Space"
x-component-props={{
//size数组代表Space的间隙,8是水平的间距是8px,20是垂直的间距是20px
size: [8, 20],
//wrap是指元素超出一行放不完的时候,可以环绕的方式放下一行
wrap: true,
}}
>
<SchemaField.String
name="input1"
title="输入框1"
x-decorator="FormItem"
x-component="Input"
x-component-props={{}}
required
/>
<SchemaField.String
name="input2"
title="输入框2"
x-decorator="FormItem"
x-component="Input"
x-component-props={{}}
required
/>
<SchemaField.String
name="input3"
title="输入框3"
x-decorator="FormItem"
x-component="Input"
x-component-props={{}}
required
/>
<SchemaField.String
name="input4"
title="输入框4"
x-decorator="FormItem"
x-component="Input"
x-component-props={{}}
required
/>
<SchemaField.String
name="input5"
title="输入框5"
x-decorator="FormItem"
x-component="Input"
x-component-props={{}}
required
/>
<SchemaField.String
name="input6"
title="输入框6"
x-decorator="FormItem"
x-component="Input"
x-component-props={{}}
required
/>
</SchemaField.Void>
</SchemaField>
</Form>
</div>
);
};
Space组件可以设置是否环绕,以及横竖方向的间隔
4 数组组件
代码在这里
数组组件,是用来针对表单的字段是一个数组的时候,如何去做编辑的问题。注意,ArrayTab与FormTab的区别。
4.1 ArrayCards
import React from 'react';
import {
FormItem,
Input,
ArrayCards,
FormButtonGroup,
Submit,
} from '@formily/antd';
import {
createForm,
onFieldInputValueChange,
onFieldValueChange,
} from '@formily/core';
import { FormProvider, createSchemaField, FormConsumer } from '@formily/react';
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
ArrayCards,
},
});
const form = createForm({
effects: () => {},
});
export default () => {
//当加入了Index列以后,我们发现,各个子条目的标题是由title与Index列组合起来的
//我们来看了Index列的源代码https://github.com/alibaba/formily/blob/formily_next/packages/antd/src/array-cards/index.tsx
//https://github.com/alibaba/formily/blob/formily_next/packages/antd/src/array-base/index.tsx#L122
/*
const title = (
<span>
<RecursionField
schema={items}
name={index}
filterProperties={(schema) => {
if (!isIndexComponent(schema)) return false
return true
}}
onlyRenderProperties
/>
{props.title || field.title}
</span>
)
* Index列使用RecursionField,的onlyRenderProperties和filterProperties来只渲染items schema的x-component含有Index字符串的列
* 这是因为Content列已经占用了ObjectField的index的那么,Index列不能再用一个ObjectField来包围它。
* 那么现在问题就是在Index列没有了ObjectField的包围,它怎么知道自己在哪一行?
<ArrayBase.Item key={index} index={index}>
<Card
{...props}
onChange={() => {}}
className={cls(`${prefixCls}-item`, props.className)}
title={title}
extra={extra}
>
{content}
</Card>
</ArrayBase.Item>
从源代码可以看到,title是跟props.title以及Index列的实现有关
ArrayBase.Item = ({ children, ...props }) => {
return <ItemContext.Provider value={props}>{children}</ItemContext.Provider>
}
const ItemContext = createContext<IArrayBaseItemProps>(null)
const useIndex = (index?: number) => {
const ctx = useContext(ItemContext)
return ctx ? ctx.index : index
}
ArrayBase.Index = (props) => {
const index = useIndex()
return <span {...props}>#{index + 1}.</span>
}
Index列的实现如上,答案是,Cards实现中,每行数据都用ArrayBase.Item创造一个Context包围住它,然后Index列的实现中使用useIndex来获取当前在哪一行的这个信息
所以,title的数据的既有Index的部分,也有ArrayCards.props.title的部分
很可惜,ItemContext没有暴露出来,所以,没有办法修改这部分代码
*/
return (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Array
name="array"
maxItems={3}
x-component="ArrayCards"
x-component-props={{ title: '属性' }}
>
<SchemaField.Object>
<SchemaField.Void x-component="ArrayCards.Index" />
<SchemaField.String
name="input"
x-decorator="FormItem"
title="输入框"
required
x-component="Input"
/>
<SchemaField.Void x-component="ArrayCards.Remove" />
<SchemaField.Void x-component="ArrayCards.MoveUp" />
<SchemaField.Void x-component="ArrayCards.MoveDown" />
</SchemaField.Object>
<SchemaField.Void
x-component="ArrayCards.Addition"
x-reactions={{
//被动联动
dependencies: ['array'],
fulfill: {
state: {
visible: '{{$deps[0].length<3}}',
},
},
}}
title="添加条目"
/>
</SchemaField.Array>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
<FormConsumer>
{(form) => {
return <div>{JSON.stringify(form.values)}</div>;
}}
</FormConsumer>
</FormProvider>
);
/*
我们可以进一步推导出来,Operation列的MoveUp,MoveDown,Remove也是类似的实现,但是它们不仅需要哪一行,还需要知道在哪个Array
return (
<ArrayBase>
{renderEmpty()}
{renderItems()}
{renderAddition()}
</ArrayBase>
)
ArrayCards的实现中,包围都被一个ArrayBase包围住了
export const ArrayBase: ComposedArrayBase = (props) => {
const field = useField<ArrayField>()
const schema = useFieldSchema()
return (
<ArrayBaseContext.Provider value={{ field, schema, props }}>
{props.children}
</ArrayBaseContext.Provider>
)
}
ArrayBase的实现中,拿出了当前的field与schema作为context,传递下去
const ArrayBaseContext = createContext<IArrayBaseContext>(null)
const useArray = () => {
return useContext(ArrayBaseContext)
}
ArrayBase.Remove = React.forwardRef((props, ref) => {
const index = useIndex(props.index)
const array = useArray()
const prefixCls = usePrefixCls('formily-array-base')
if (!array) return null
if (array.field?.pattern !== 'editable') return null
return (
<DeleteOutlined
{...props}
className={cls(`${prefixCls}-remove`, props.className)}
ref={ref}
onClick={(e) => {
if (array.props?.disabled) return
e.stopPropagation()
array.field?.remove?.(index)
array.props?.onRemove?.(index)
if (props.onClick) {
props.onClick(e)
}
}}
/>
)
})
那么Remove的实现就简单了,通过useArray拿出当前在哪个array,通过useIndex拿出当前在哪个index,onClick的时候,执行array的remove就可以了
*/
};
ArrayCards的功能与实现
4.2 ArrayTable
4.2.1 基础
import React from 'react';
import {
FormItem,
Input,
ArrayTable,
Editable,
FormButtonGroup,
Submit,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField, FormConsumer } from '@formily/react';
import { Button, Alert } from 'antd';
const SchemaField = createSchemaField({
components: {
FormItem,
Editable,
Input,
ArrayTable,
},
});
const form = createForm();
const range = (count: number) =>
Array.from(new Array(count)).map((_, key) => ({
aaa: key,
}));
export default () => {
return (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Array
name="array"
x-decorator="FormItem"
x-component="ArrayTable"
x-component-props={{
pagination: { pageSize: 10 },
scroll: { x: '100%' },
}}
>
<SchemaField.Object>
<SchemaField.Void
//描述每一个行,这个跟ArrayCards的不同。
//ArrayCards下面直接就是字段
//ArrayTable下面先有列描述,再有字段
x-component="ArrayTable.Column"
x-component-props={{
width: 50,
title: 'Sort', //列标题
align: 'center', //标题的排列情况
}}
>
<SchemaField.Void
x-decorator="FormItem"
required
x-component="ArrayTable.SortHandle" //内置的操作
/>
</SchemaField.Void>
<SchemaField.Void
x-component="ArrayTable.Column"
x-component-props={{
width: 80,
title: 'Index',
align: 'center',
}}
>
<SchemaField.String
x-decorator="FormItem"
required
x-component="ArrayTable.Index" //内置的排序
/>
</SchemaField.Void>
<SchemaField.Void
x-component="ArrayTable.Column"
x-component-props={{
title: 'A1',
dataIndex: 'a1', //数据位置描述,可省略
width: 200, //行宽度,固定的
}}
>
<SchemaField.String
name="a1"
x-decorator="FormItem" //不是FormItem,是Editable
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
x-component="ArrayTable.Column"
x-component-props={{
title: 'A2', //这里就没有dataIndex的描述
width: 200,
}}
>
<SchemaField.String
x-decorator="FormItem"
name="a2"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
x-component="ArrayTable.Column"
x-component-props={{ title: 'A3', width: 200 }}
>
<SchemaField.String
x-decorator="FormItem"
name="a3"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
x-component="ArrayTable.Column"
x-component-props={{
title: 'Operations',
dataIndex: 'operations',
width: 200,
//fixed为right,就是列固定在右边,不动,像fixed布局的一样
fixed: 'right',
}}
>
<SchemaField.Void
//FormItem也是可以放在Component里面的
x-component="FormItem"
>
<SchemaField.Void x-component="ArrayTable.Remove" />
<SchemaField.Void x-component="ArrayTable.MoveDown" />
<SchemaField.Void x-component="ArrayTable.MoveUp" />
</SchemaField.Void>
</SchemaField.Void>
</SchemaField.Object>
<SchemaField.Void
x-component="ArrayTable.Addition"
//默认的添加一行操作
//method 'push' |'unshift',可以设置为头部插入,还是尾部插入
title="Add entry"
/>
</SchemaField.Array>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
<Button
block
onClick={() => {
//强行初始化数据
form.setInitialValues({
array: range(100000),
});
}}
>
Load 10W pieces of large data
</Button>
</FormButtonGroup>
<FormConsumer>
{(form) => {
return <div>{JSON.stringify(form.values)}</div>;
}}
</FormConsumer>
<Alert
style={{ marginTop: 10 }}
message="Note: Open the formily plug-in page, because there is data communication in the background, which will occupy the browser's computing power, it is best to test in the incognito mode (no formily plug-in)"
type="warning"
/>
</FormProvider>
);
};
ArrayTable是最重要的实现,经常会用到,我们在Formily的Core库中也探讨过它的实现了。注意,它的items组件下是嵌套有Column组件的,而ArrayCards组件是没有这样的Column组件的。
4.2.2 列组件联动
import React from 'react';
import {
FormItem,
Input,
ArrayTable,
Editable,
FormButtonGroup,
Submit,
} from '@formily/antd';
import {
createForm,
onFieldInputValueChange,
onFieldValueChange,
} from '@formily/core';
import { FormProvider, createSchemaField, FormConsumer } from '@formily/react';
import { Button, Alert } from 'antd';
const SchemaField = createSchemaField({
components: {
FormItem,
Editable,
Input,
ArrayTable,
},
});
const form = createForm({
effects: () => {
onFieldInputValueChange('array', (field) => {
form.setFieldState('array.firstColumn', (state) => {
let compontProps = state.componentProps;
if (compontProps) {
compontProps.title = 'Sort:' + field.value.length + 'Item';
}
});
});
},
});
const range = (count: number) =>
Array.from(new Array(count)).map((_, key) => ({
aaa: key,
}));
export default () => {
return (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Array
name="array"
x-decorator="FormItem"
x-component="ArrayTable"
x-component-props={{
pagination: { pageSize: 10 },
scroll: { x: '100%' },
}}
>
<SchemaField.Object>
<SchemaField.Void
name="firstColumn"
//描述每一个行,这个跟ArrayCards的不同。
//ArrayCards下面直接就是字段
//ArrayTable下面先有列描述,再有字段
x-component="ArrayTable.Column"
x-component-props={{
width: 50,
title: 'Sort', //列标题
align: 'center', //标题的排列情况
}}
>
<SchemaField.Void
x-decorator="FormItem"
required
x-component="ArrayTable.SortHandle" //内置的操作
/>
</SchemaField.Void>
<SchemaField.Void
x-component="ArrayTable.Column"
x-component-props={{
width: 80,
title: 'Index',
align: 'center',
}}
>
<SchemaField.String
x-decorator="FormItem"
required
x-component="ArrayTable.Index" //内置的排序
/>
</SchemaField.Void>
<SchemaField.Void
x-component="ArrayTable.Column"
x-component-props={{
title: 'A1',
dataIndex: 'a1', //数据位置描述,可省略
width: 200, //行宽度,固定的
}}
>
<SchemaField.String
name="a1"
x-decorator="FormItem" //不是FormItem,是Editable
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
x-component="ArrayTable.Column"
x-component-props={{
title: 'A2', //这里就没有dataIndex的描述
width: 200,
}}
>
<SchemaField.String
x-decorator="FormItem"
name="a2"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
x-component="ArrayTable.Column"
x-component-props={{ title: 'A3', width: 200 }}
>
<SchemaField.String
x-decorator="FormItem"
name="a3"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
x-component="ArrayTable.Column"
x-component-props={{
title: 'Operations',
dataIndex: 'operations',
width: 200,
//fixed为right,就是列固定在右边,不动,像fixed布局的一样
fixed: 'right',
}}
>
<SchemaField.Void
//FormItem也是可以放在Component里面的
x-component="FormItem"
>
<SchemaField.Void x-component="ArrayTable.Remove" />
<SchemaField.Void x-component="ArrayTable.MoveDown" />
<SchemaField.Void x-component="ArrayTable.MoveUp" />
</SchemaField.Void>
</SchemaField.Void>
</SchemaField.Object>
<SchemaField.Void
x-component="ArrayTable.Addition"
//默认的添加一行操作
//method 'push' |'unshift',可以设置为头部插入,还是尾部插入
title="Add entry"
/>
</SchemaField.Array>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
<Button
block
onClick={() => {
//强行初始化数据
form.setInitialValues({
array: range(100000),
});
}}
>
Load 10W pieces of large data
</Button>
</FormButtonGroup>
<FormConsumer>
{(form) => {
return <div>{JSON.stringify(form.values)}</div>;
}}
</FormConsumer>
<Alert
style={{ marginTop: 10 }}
message="Note: Open the formily plug-in page, because there is data communication in the background, which will occupy the browser's computing power, it is best to test in the incognito mode (no formily plug-in)"
type="warning"
/>
</FormProvider>
);
};
ArrayTable的列组件是支持数据联动,不仅仅是一个占位符的Schema
4.3 ArrayTab
import React from 'react';
import {
FormItem,
Input,
ArrayTabs,
FormButtonGroup,
Submit,
} from '@formily/antd';
import { createForm } from '@formily/core';
import { FormProvider, createSchemaField, FormConsumer } from '@formily/react';
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
ArrayTabs,
},
});
const form = createForm();
export default () => {
//跟arrayCards相似的实现,+号是默认组件,无法去掉,触发+号标签的时候就会新建一个标签
return (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Array
name="array"
x-decorator="FormItem"
title="对象数组"
maxItems={3}
x-component="ArrayTabs"
>
<SchemaField.Object>
<SchemaField.String
x-decorator="FormItem"
title="AAA"
name="aaa"
required
x-component="Input"
/>
<SchemaField.String
x-decorator="FormItem"
title="BBB"
name="bbb"
required
x-component="Input"
/>
</SchemaField.Object>
</SchemaField.Array>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
<FormConsumer>
{(form) => {
return <div>{JSON.stringify(form.values)}</div>;
}}
</FormConsumer>
</FormProvider>
);
};
ArrayTabs与ArrayCards就类似了,不过要注意,它是不能控制自增组件的,这个组件的实现并不顺手。
5 总结
数组数据组件,重点也是难掌握的部分,在业务中,我们更多是需要针对自己的场景定制UI组件。
- 本文作者: fishedee
- 版权声明: 本博客所有文章均采用 CC BY-NC-SA 3.0 CN 许可协议,转载必须注明出处!