Formily的Antd经验汇总

2021-07-25 fishedee 前端

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组件。

相关文章