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

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

一、浅谈表单封装

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

二、谈论使用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类型,支持简单配置出多层复杂的表单组件;

举个官网上的🌰

js 复制代码
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,以供不同外层容器中的表单使用。
tsx 复制代码
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组件,注意预设配置要支持上层组件修改噢
tsx 复制代码
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>
  );
};

四、参考文章

相关推荐
xjt_09011 分钟前
浅析Web存储系统
前端
foxhuli22939 分钟前
禁止ifrmare标签上的文件,实现自动下载功能,并且隐藏工具栏
前端
青皮桔1 小时前
CSS实现百分比水柱图
前端·css
失落的多巴胺1 小时前
使用deepseek制作“喝什么奶茶”随机抽签小网页
javascript·css·css3·html5
DataGear1 小时前
如何在DataGear 5.4.1 中快速制作SQL服务端分页的数据表格看板
javascript·数据库·sql·信息可视化·数据分析·echarts·数据可视化
影子信息1 小时前
vue 前端动态导入文件 import.meta.glob
前端·javascript·vue.js
青阳流月1 小时前
1.vue权衡的艺术
前端·vue.js·开源
样子20181 小时前
Vue3 之dialog弹框简单制作
前端·javascript·vue.js·前端框架·ecmascript
kevin_水滴石穿1 小时前
Vue 中报错 TypeError: crypto$2.getRandomValues is not a function
前端·javascript·vue.js
翻滚吧键盘1 小时前
vue文本插值
javascript·vue.js·ecmascript