Skip to content
Return to top
On this page

# defineFatForm 定义器 #

和 `defineFatTable` 类似， 我们也提供了 defineFatForm 用于快速定义表单。相比直接使用 `<template>`， defineFatForm 可以提供更好的类型提示和检查。

## Hello world #

查看代码
tsx

    import { defineFatForm } from '@wakeadmin/components';

    export default defineFatForm<{
      // 🔴 这里的泛型变量可以定义表单数据结构
      name: string;
      nickName: string;
    }>(({ item, form, consumer, group }) => {
      // 🔴 这里可以放置 Hooks

      // 🔴 form 为 FatForm 实例引用
      console.log(form);

      // 返回表单定义
      return () => ({
        // FatForm props 定义
        initialValue: {
          name: 'ivan',
          nickName: '狗蛋',
        },

        // 🔴 子节点
        children: [
          item({ prop: 'name', label: '账号名' }),
          item({
            prop: 'nickName',
            label: '昵称',
          }),

          // 🔴 这里甚至可以放 JSX
          <div>JSX hello</div>,

          // 🔴 不过，如果你想要监听 表单数据，还是建议使用 FatFormConsumer, 否则会导致整个表单的重新渲染
          // 不信，你可以打开 Vue 开发者工具的 Highlight Updates 试一下
          consumer(({ values }) => {
            return group({
              label: '表单状态',
              children: (
                <pre>
                  <code>{JSON.stringify(values, null, 2)}</code>
                </pre>
              ),
            });
          }),
        ],
      });
    });

`defineFatForm` 使用方法类似于 Vue 的 `defineComponent`, 只不过返回一个 `DSL`, 这个 DSL 定义了表单的结构和配置信息。

## 定义结构 #

defineFatForm 的 `DSL` 需要满足类似 `<template>` 的灵活结构的同时，保持一定约束性。为此我们提供了一些 `helpers` 来辅助你定义这个 DSL，并且能关联类型上下文。

- `group` 定义 `FatFormGroup`
- `item` 定义 `FatFormItem`
- `section` 定义 `FatFormSection`
- `consumer` 定义 `FatFormConsumer`
- `table` 定义 `FatFormTable`
- `tableColumn` 定义 `FatFormTable#columns` 子项

底层方法

- renderChild 可以将上述辅助转换为 JSX 节点
- renderChildren 和 renderChild 类似，只不过支持接收一个数组

上述 `helper` 接收对应组件的 Props，除了 `item` , `group`、`section` 还支持传入一个 `children` 字段, 用于定义下级节点:

tsx

    defineFatForm(({ item, group, section, consumer }) => {
      return () => ({
        // 这里放置 FatForm 的 Props
        // ...

        // 定义下级节点
        children: [
          group({
            children: [
              // group 支持下级节点
            ],
          }),
          section({
            children: [
              // section 支持下级节点
              group({
                // 可以逐级嵌套
                children: [],
              }),
            ],
          }),
          consumer(() => {
            // consumer 回调的返回值等价于 children
            return [];
          }),
        ],
      });
    });

`children` 支持传入 `item`、`group`、`section`、`consumer`, 以及 JSX:

tsx

    defineFatForm(({ item, group, section, consumer }) => {
      return () => ({
        // 定义下级节点
        children: [
          group({
            /*..*/
          }),
          item({
            /*..*/
          }),
          section({
            /*..*/
          }),
          consumer(() => {
            /*..*/
          }),
          <div>JSX</div>,
          someCondition && <div>JSX</div>,
        ],
      });
    });

## 为什么推荐使用 `consumer` 而不是直接写 `JSX` #

如果想要实现表单联动等, 依赖于表单数据的渲染，我们推荐使用 consumer, 因为它能够实现精确渲染。下面对比两个示例(打开 Vue 开发者工具的 Highlight Updates 查看重新渲染的范围):

查看代码
tsx

    import { defineFatForm } from '@wakeadmin/components';

    export const UseConsumer = defineFatForm(({ item, consumer }) => {
      return () => ({
        children: [
          item({ label: 'a', prop: 'a' }),
          item({ label: 'b', prop: 'b' }),
          item({ label: 'c', prop: 'c' }),
          consumer(({ values }) => {
            return <div>{JSON.stringify(values)}</div>;
          }),
        ],
      });
    });

    /**
     * 未使用 consumer, 将导致全量渲染
     */
    export const NotUseConsumer = defineFatForm(({ item, form }) => {
      return () => ({
        children: [
          item({ label: 'a', prop: 'a' }),
          item({ label: 'b', prop: 'b' }),
          item({ label: 'c', prop: 'c' }),
          <div>{JSON.stringify(form.value?.values)}</div>,
        ],
      });
    });

## 示例 #

来看一个相对复杂的案例，这个案例来源于惟客云(会员中心/积分倍率活动)。这个例子会展示 checkboxs 原件，FatFormGroup 的妙用：

查看代码
tsx

    import { defineFatForm } from '@wakeadmin/components';

    enum Type {
      Or,
      And,
      None,
    }

    interface Values {
      type: Type;
      conditions: string[];
    }

    export default defineFatForm<Values>(({ group, renderChild, item }) => {
      return () => {
        return {
          initialValue: {
            type: Type.Or,
            conditions: [],
          },
          layout: 'vertical',
          children: [
            group({
              label: '触发条件',
              gutter: 'sm',
              vertical: true,
              children: [
                item({
                  prop: 'type',
                  valueType: 'radio',
                  valueProps: {
                    options: [
                      {
                        label: '满足一项选中条件即可',
                        value: Type.Or,
                      },
                      {
                        label: '满足全部选中条件',
                        value: Type.And,
                      },
                      {
                        label: '无限制',
                        value: Type.None,
                      },
                    ],
                  },
                  // 依赖于 conditions, 即 conditions 变化时会触发它重新验证
                  dependencies: 'conditions',
                  rules: values => ({
                    validator(_rule, value, callback) {
                      if (value !== Type.None && !values.conditions?.length) {
                        callback(new Error('请选择至少一条触发时间条件'));
                      } else {
                        callback();
                      }
                    },
                  }),
                }),
                // group 可以控制下级的一些状态，比如 disabled
                group({
                  disabled: f => f.values.type === Type.None,
                  children: [
                    item({
                      prop: 'conditions',
                      valueType: 'checkboxs',
                      valueProps: {
                        // 垂直布局
                        vertical: true,
                        options: [
                          {
                            label: active =>
                              renderChild(
                                group({
                                  // 当选项未选中时禁用表单, 如果选中，传入 undefined, 让 group 从父级继承 disabled 状态
                                  disabled: !active ? true : undefined,
                                  children: [
                                    '每年',
                                    item({
                                      prop: 'year.dateRange',
                                      valueType: 'date-range',
                                      required: true,
                                      width: 'small',
                                    }),
                                    '时间段',
                                    item({
                                      prop: 'year.timeRange',
                                      valueType: 'time-range',
                                      required: true,
                                      width: 'small',
                                    }),
                                  ],
                                })
                              ),
                            value: 'year',
                          },
                          {
                            label: active =>
                              renderChild(
                                group({
                                  // 当选项未选中时禁用表单
                                  disabled: !active ? true : undefined,
                                  children: [
                                    '每月',
                                    item({
                                      prop: 'month.date',
                                      valueType: 'select',
                                      valueProps: {
                                        options: [{ value: 1, label: '1' }],
                                      },
                                      width: 'mini',
                                      required: true,
                                    }),
                                    '时间段',
                                    item({
                                      prop: 'month.timeRange',
                                      width: 'small',
                                      valueType: 'time-range',
                                      required: true,
                                    }),
                                  ],
                                })
                              ),
                            value: 'month',
                          },
                        ],
                      },
                    }),
                  ],
                }),
              ],
            }),
          ],
        };
      };
    });

要点：

- `FatFormGroup` 配置的状态可以被下级继承。这些状态包含 mode, size, disabled, hidden, hideMessageOnPreview, clearable, col 等。如果下级显式配置了这些属性，那么将以下级的优先。
- `renderChild` 用于将 item, group, consumer, section 等 helper 的输出转换为 JSX。这是一个底层方法，用于一些复杂的场景。
- 当 `FatFormItem` 的状态为 hidden，disabled 或者 mode 为 'preview' 时，针对该字段的验证规则也会自动移除。
- `rules` 支持传入一个函数，用于依赖上下文的一些验证。 另外一个配合 dependencies 配置依赖的其他字段，当这些字段变化时重新验证。
- 最佳实践是在 FatFormItem 上配置 rule，而不是在 FatForm 上。这样更加灵活，尤其是在动态表单的场景。