likes
comments
collection
share

从antdPro的SchemaForm学习组件封装思路及一些使用经验分享

作者站长头像
站长
· 阅读数 18

最近开发一个全新的项目,由于项目时间比较紧张,选用了开箱直用的antd ProComponents来开发表单,引发了一些小小思考,在此记录一下📝~

一、浅谈表单封装

作为前端人儿,一般都对表单再熟悉不过了。如果直接用 Form.Item去写每一个表单项,不仅代码臃肿,而且不利于复用。对于开发有一定设计规范(具有比较稳定的风格和预设行为)的软件系统而言,都是可以二次封装表单,再通过写json的形式配置出批量表单的。

从antdPro的SchemaForm学习组件封装思路及一些使用经验分享

二、谈论使用SchemaForm的感受和学习到的地方

1、之前用material ui封装表单的问题

① 子组件核心表单项的props平铺展开暴露给父组件,与其他配置项属性混杂,不利于后期维护;

② 表单项没有配置slot,不利于拓展;

③ 没有向顶层组件暴露form实例的属性及方法,单纯依靠定义多个setState获取所有表单数值、当前变化表单数据、提交时校验时的首个错误表单项等,数据一层层传递很麻烦;

④ 多层组件的字段和单层组件定义不同,无法直接简单配置出混合单层及多层的表单

······

2、从使用SchemaForm引发的一些思考

SchemaForm 是根据 JSON Schema 来生成表单的工具。SchemaForm 会根据 valueType 来映射成不同的表单项。

封装组件时要注重可拓展性,支持上层组件灵活调整显示效果,以支持更多更复杂的需求;

  • 枚举类的配置项不要写死,尽量支持自定义。如SchemaForm依赖valueType映射表单项,不仅自定义了一些预设type,还支持自定义。
  • 预留slot/planB,支持自定义不同条件下的展示组件。如SchemaForm定义了renderFormItem,自定义编辑模式,返回一个 ReactNode,会自动包裹 value 和 onChange。
  • 定义更灵活的数据结构,支持map数据类型/多层数组等。如SchemaForm的columns支持多重“套娃🪆🪆”,valueType也有group类型,支持简单配置出多层复杂的表单组件;

举个官网上的🌰

const columns = [
    {
      title: '列表',
      valueType: 'formList',
      dataIndex: 'list',
      initialValue: [{ state: 'all', title: '标题' }],
      columns: [
        {
          valueType: 'group',
          columns: [
            {
              title: '状态',
              dataIndex: 'state',
              valueType: 'select',
              width: 'xs',
              valueEnum,
            },
            {
              title: '标题',
              dataIndex: 'title',
              formItemProps: {
                rules: [
                  {
                    required: true,
                    message: '此项为必填项',
                  },
                ],
              },
              width: 'm',
            },
          ],
        },
      ],
    },
 ];

三、基于SchemaForm封装更符合项目特点的表单--思路分享

由于antd-pro的SchemaForm在数据联动、数据转化、数据校验、样式布局、复杂表单项显示等各个方面都预设了很多行为,一般这些方面直接拿来用即可,所以基于SchemaForm封装表单,我们一般要做的就是结合项目特点做一些统一配置,如

  • 自行封装其他valueType方便快速使用;
  • 在columns传递给SchemaForm时做一些统一处理以免去重复配置,注意仍要支持上层组件自定义。

代码示例🌰

  • 定义ProFormProvider组件,运用useContext预设更多valueType,以供不同外层容器中的表单使用。
import { useContext } from 'react';
import { ProProvider } from '@ant-design/pro-provider';
import { Counter, InputWithSelectBefore, RadioWithInput } from './components';
import { ValueTypes } from '.';
import { ProFormProviderContext } from './context';

export const ProFormProvider = (props) => {
    const { children, valueTypeMap = {}, formRef, ...rest } = props;
    const providerValues = useContext(ProProvider);
    return (
        <ProFormProviderContext.Provider value={{ formRef, ...rest }}>
            <ProProvider.Provider
                value={{
                    ...providerValues,
                    valueTypeMap: {
                        ...valueTypeMap,
                        [ValueTypes.counter]: {
                            render: (text) => (text),
                            renderFormItem: (text, props, dom) => (
                                <Counter {...props} {...props?.fieldProps} />
                            ),
                        },
                    }
                }}>
                {children}
            </ProProvider.Provider>
        </ProFormProviderContext.Provider>
    );
};
  • 定义自定义表单项组件,关注value和onChange

可参考官网示例🌰——自定义valueType

  • 外层直接使用的SchemaForm组件,注意预设配置要支持上层组件修改噢
import { useMemo, memo } from 'react';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { ProFormProvider } from '@/components';
import { useTranslation } from 'react-i18next';
import { transformColumns } from './util';

const SchemaForm = (props) => {
  const {
    layoutType = 'Form',
    size = 'large',
    initialValues,
    columns,
    formRef,
    valueTypeMap,
    submitter = false,
    onFinish,
    onValuesChange,
    ...rest
  } = props;
  const { t } = useTranslation();

 // 利用transformColumns方法做一些统一处理。
  const _columns = useMemo(() => {
    return transformColumns(columns, t);
  }, [columns]);

  return (
    <ProFormProvider valueTypeMap={valueTypeMap} formRef={formRef} >
      <BetaSchemaForm
        autoFocusFirstInput={false}
        formRef={formRef}
        layoutType={layoutType}
        size={size}
        grid={true}
        rowProps={{ gutter: [30, 0] }}
        labelCol={{ flex: '0 0 40px' }}
        initialValues={initialValues}
        columns={_columns}
        submitter={submitter}
        onFinish={async (values) => {
          onFinish && onFinish(values);
        }}
        onValuesChange={(changeValues, values) => {
          onValuesChange && onValuesChange(changeValues, values);
        }}
        {...rest}
      />
    </ProFormProvider>
  );
};

四、参考文章