React hook+AntD pro实现Form表单的二次封装

发布时间:2024年01月19日

封装Form表单

关键hooks API: useImperativeHandle、useRef,高阶组件: forwardRef,

1、在src/types下新建 antd/form/index.ts,进行Form表的配置、数据等类型的限制

import React, { ReactNode } from 'react';
import { IButton } from '@/types/antd/button';
// @ts-ignore
import { FilterFunc, GetFieldsValueConfig } from 'rc-field-form/es/interface';


type RadioGroupOption = {
  buttonStyle?: 'outline' | 'solid';
  name?: string;
  options?: any[];
  optionType?: 'default' | 'button',
  size?: 'large' | 'middle' | 'small',
}

type SelectOptions = {
  mode?: 'multiple' | 'tags';
  options?: any[];
}
export type FormOptions = {
  // 表单名称,会作为表单字段 id 前缀使用
  name: string;
  labelCol?: number;
  wrapperCol?: number;
  autoComplete?: string;
  // label 是否显示冒号
  colon?: boolean;
  // label 标签的文本对齐方式
  labelAlign?: 'right' | 'left';
  // 表单布局
  layout?: 'horizontal' | 'vertical' | 'inline';
  // 设置字段组件的尺寸(仅限 antd 组件)
  size?: 'small' | 'middle' | 'large';
  // initialValues?: {[index: string]: any}
}

export type FormItemOptions = {
  // 是否显示 label 后面的冒号
  colon?: boolean;
  // label 标签的文本
  label?: ReactNode | string;
  placeholder?: string;
  name: string;  // use is key
  // 标签文本对齐方式
  labelAlign?: 'left' | 'right';
  labelCol?: number;
  wrapperCol?: number;
  noStyle?: boolean;
  required?: boolean;
  // 设置防抖,延迟毫秒数后进行校验
  validateDebounce?: number;
  // 设置字段校验的时机 : onChange
  validateTrigger?: string | string[];
  // input 图标
  prefix?: React.ReactElement;
  formItemType: string;
  rules?: {[index: string]: string | boolean}[];
  formButtons?: IButton[],
  rows?: number;
  allowClear?: boolean;
  selectOptions?: SelectOptions;
  radioOptions?: RadioGroupOption;
}

export interface IFormProps {
  formOptions: FormOptions;
  formValue: {[index: string]: any};
  formItemOptions: FormItemOptions[];
  emitSubmit?: (formData: any) => void;
}

// SystemForm 组件暴露的数据结构
export interface IFormCompExportData {
  getFieldsValue: (() => any) & ((nameList: (true | any[]), filterFunc?: (FilterFunc | undefined)) => any) & ((config: GetFieldsValueConfig) => any)
}

2、在 根目录/components 下新建 BaseForm/index.tsx文件

子组件:Form的二次封装组件

import React, { forwardRef, useImperativeHandle } from 'react';
import { IFormCompExportData, IFormProps } from '@/types/antd/form';
import createFormIpt from './createFormIpt';
import { Form } from 'antd';

const SystemForm = (props: IFormProps, ref: React.Ref<any>) => {
  const {
    formOptions,
    formItemOptions,
    emitSubmit,
    formValue,
  } = props;
  const [form] = Form.useForm();

  // useImperativeHandle: 细化ref暴露的实例粒度
  useImperativeHandle(ref, (): IFormCompExportData=>({
    // 这里可以暴露SystemForm组件的所有内容  变量、方法、元素实例
    // 避免暴露出 完整的Form表单实例form  这里选择暴露获取字段value方法getFieldsValue的引用
    getFieldsValue: form.getFieldsValue
  }), [])

  const onFinish = (values: any) => {
    emitSubmit && emitSubmit(values);
  };


  return (
    <Form
      form={form}
      name={formOptions.name}
      labelCol={{ span: formOptions.labelCol }}
      wrapperCol={{ span: formOptions.wrapperCol }}
      initialValues={formValue}
      autoComplete={formOptions.autoComplete}
      size={formOptions.size}
      onFinish={onFinish}
    >
      {
        formItemOptions.map(item => {
          return (
            <Form.Item
              key={item.name}
              label={item.label}
              name={item.name}
              rules={item.rules}
            >
              {/*<CreateFormIpt formItem={item} />*/}
              {createFormIpt(item)}
            </Form.Item>
          );
        })
      }

    </Form>
  );
};

// 因为有forwardRef包裹,所以SystemForm组件才可以使用第二个参数ref
export default forwardRef(SystemForm);

3、在BaseForm/createFormIpt.tsx中,抽取对不同类型的表单元素渲染

import { FormItemOptions } from '@/types/antd/form';
import React from 'react';
import { Button, Input, Select, Radio } from 'antd';


const { TextArea} = Input;

const createFormIpt  = (formItem: FormItemOptions) => {

  if(formItem.name === 'password') {
    return (
      <Input type="password" prefix={formItem.prefix} placeholder={formItem.placeholder}/>
    )
  }

  const iptRenderMapByFormItemType: {[index: string]: React.JSX.Element} = {
    'input': <Input prefix={formItem.prefix} placeholder={formItem.placeholder} allowClear={formItem.allowClear}/>,
    'textarea':<TextArea  rows={formItem.rows}  placeholder={formItem.placeholder} allowClear={formItem.allowClear} />,
    'radio': <Radio.Group options={formItem.radioOptions?.options}/>,
    'select': <Select
      placeholder={formItem.placeholder}
      mode={formItem.selectOptions?.mode}
      allowClear={formItem.allowClear}
      options={formItem.selectOptions?.options}
    />,
    'button': <>
      {formItem.formButtons?.map(itemBtn=>(
        <Button
          key={itemBtn.key}
          type={itemBtn.buttonType}
          loading={itemBtn.btnLoadingStatus}
          block={itemBtn.block}
          htmlType={itemBtn.htmlType}
        >
            {itemBtn.btnDesc}
        </Button>
      ))}
    </>,
  }

  return iptRenderMapByFormItemType[formItem.formItemType]
}

export default createFormIpt;

4、在src/pages/PublishArticle/下,新建驱动表单的数据文件data.ts

import { FormOptions, FormItemOptions } from '@/types/antd/form';

export const publishArticleFormValue = {
  'title': '11s',
  'content': '',
  'summary': '',
  'categoryId': '',
  'tags': [],
  'isComment': '0',
  'isTop': '0',
  'thumbnail': '',
}


// 文章表单
export const publishArticleFormOptions: FormOptions = {
  name: 'publishArticle',
  autoComplete: 'off',
  size: 'large',
  labelCol: 4,
  wrapperCol: 20,
}


export const publishArticleFormData: FormItemOptions[] = [
  {
    label: '文章标题',
    name: 'title',
    placeholder: '请输入文章标题',
    formItemType: 'input',
    allowClear: true,
  },
  {
    label: '文章摘要',
    name: 'summary',
    placeholder: '请输入文章摘要',
    formItemType: 'textarea',
    allowClear: true,
  },
  {
    label: '分类',
    name: 'categoryId',
    placeholder: '请选择',
    formItemType: 'select',
    selectOptions: {

    },
    allowClear: true,
  },
  {
    label: '标签',
    name: 'tags',
    placeholder: '请选择',
    formItemType: 'select',
    selectOptions: {
      mode: 'multiple'
    },
    allowClear: true,
  },
  {
    label: '允许评论',
    name: 'isComment',
    formItemType: 'radio',
    radioOptions: {
      options: [
        { label: '正常', value: '1' },
        { label: '停用', value: '0' }
      ]
    }
  },
  {
    label: '是否置顶',
    name: 'isTop',
    formItemType: 'radio',
    radioOptions: {
      options: [
        { label: '是', value: '1' },
        { label: '否', value: '0' }
      ]
    }
  },
]

5、在src/pages/PublishArticle/下,新建使用二次封装的Form组件的父组件index.ts

import React, { useRef, useState } from 'react';
import { PageContainer } from '@ant-design/pro-components';
import {
  publishArticleFormData,
  publishArticleFormOptions,
  publishArticleFormValue,
} from '@/pages/PublishArticle/data';
import { IFormCompExportData } from '@/types/antd/form';


const PublishArticle: React.FC = () => {
  // useRef()  其 .current 属性被初始化为传入的参数(initialValue)
  // 所以useRef的初始化数据类型和useImperativeHandle返回的handle对象数据类型是一致的
  const publishArticleFormRef = useRef<IFormCompExportData>(null);

  const test = () => {
    const { getFieldsValue } = publishArticleFormRef.current as IFormCompExportData;
    // 获取表单的所有值getFieldsValue(true)
    console.log('表单收集的值', getFieldsValue(true));
  }
  return (
    <PageContainer>
      <BaseForm
         formOptions={publishArticleFormOptions}
         formItemOptions={publishArticleFormData}
         formValue={publishArticleFormValue}
         ref={publishArticleFormRef}
       />
       <Button type="primary" onClick={test}>获取表单动态值</Button>
    </PageContainer>
  );
};

export default PublishArticle;
文章来源:https://blog.csdn.net/weixin_44224921/article/details/135689455
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。