简易的antd form封装

152 阅读3分钟

1.目标:封装antdfrom,支持表单像antd table一样配置化

2.具体步骤

2.1新建index.tsx,这个文件定义接口以及form的入口

import type {
  CheckboxOptionType,
  FormInstance,
  FormItemProps,
  FormProps,
  RadioGroupProps,
  SelectProps,
  TreeSelectProps,
} from 'antd'
import { Form, Space } from 'antd'
import type {} from 'antd/es/form'
import { uniqueId } from 'lodash-es'
import React, { useImperativeHandle, useMemo } from 'react'
import FormItemComponent from './FormItemComponent'
import FormItemObj from './FormItemObj'
interface CustFormProps extends FormProps {
  /**
   * @description 表单内容数组
   */

  formItems: CustFormItemProps[]
}
export type CustFormInstance = { form: FormInstance }
type RenderFormItemPropsType = Partial<{
  //对照 antd补类型,不要加[propName:string]: any;
  options: SelectProps['options'] | RadioGroupProps['options'] | CheckboxOptionType[]
  disabled: boolean
  mode: 'multiple' | 'tags'
  size: 'large' | 'middle' | 'small'
  treeData: TreeSelectProps['treeData']
  maxLength: number
  allowClear: boolean | { clearIcon?: React.ReactNode }
  showSearch: boolean
  optionFilterProp: string
  filterOption: SelectProps['filterOption']
  notFoundContent: React.ReactNode
  labelInValue: boolean
}> &
  React.HTMLAttributes<HTMLElement>
export interface CustFormItemProps {
  key?: string
  type: keyof typeof FormItemObj
  /**
   * @description 传递给Form.Item的Props
   */
  antdFormItemProps?: Omit<FormItemProps, 'name' | 'label'> & {
    shouldUpdateCallbackFn?: (form: FormInstance, custRenderObj: React.ReactNode) => React.ReactNode
  }
  label?: React.ReactNode
  name?: string
  /**
   *@description 传递给Form.Item子组件的Props
   */
  renderFormItemProps?: RenderFormItemPropsType
  children?: CustFormItemProps[]
  /**
   *@description 但存在children是否用<Space.compact>连接
   */

  isCompact?: boolean
}
export type FormItemType = Omit<CustFormItemProps, 'colProps'>
const CustForm = React.memo(
  React.forwardRef<CustFormInstance, CustFormProps>((props, ref) => {
    const { formItems, layout = 'horizontal', ...args } = props
    const [form] = Form.useForm()
    useImperativeHandle(ref, () => ({
      form: form,
    }))
    //默认layout ==='horizontal',表单元素布局
    const initFormItemLayout = useMemo(() => {
      return layout === 'horizontal' ? { labelcol: { span: 5 }, wrappercol: { span: 17 } } : null
    }, [layout])
    const loopRender = (formItems: CustFormItemProps[], isCompact: boolean) => {
      const list = formItems.map(({ children, isCompact = true, ...argsProps }) => {
        let child
        let key: string
        if (argsProps.key) {
          key = argsProps.key
        } else if (argsProps.name) {
          key = argsProps.type + argsProps.name
        } else {
          key = uniqueId() + argsProps.type
        }
        if (children && children.length > 0) {
          child = (
            <Form.Item
              key={key}
              label={argsProps.label}
              {...argsProps.antdFormItemProps}
              name={undefined}
            >
              {loopRender(children, isCompact)}
            </Form.Item>
          )
        } else {
          child = <FormItemComponent key={key} {...argsProps} />
        }
        return child
      })
      return isCompact ? <Space.Compact>{list}</Space.Compact> : list
    }
    return (
      <Form scrollToFirstError {...initFormItemLayout} layout={layout} {...args} form={form}>
        {loopRender(formItems, false)}
      </Form>
    )
  })
)
export default CustForm

2.2新加FormItemComponent文件,这个文件负责表单元素处理逻辑

import { Form, FormInstance } from 'antd'
import React from 'react'
import FormItemObj from './FormItemObj'
import type { FormItemType } from './index'
const FormItemComponent: React.FC<FormItemType> = React.memo((props) => {
  const { name, type, label, antdFormItemProps } = props
  if (type) {
    let formDiv
    const { shouldUpdate, dependencies, shouldUpdateCallbackFn, ...antdFormItemArgesProps } =
      antdFormItemProps || {}
    if (shouldUpdate && shouldUpdateCallbackFn) {
      formDiv = (form: FormInstance) => {
        const result = shouldUpdateCallbackFn(
          form,
          <Form.Item
            valuePropName={['switch', 'checkbox'].includes(type) ? 'checked' : undefined}
            name={name}
            label={label}
            {...antdFormItemArgesProps}
          >
            {FormItemObj[type](props)}
          </Form.Item>
        )
        return result
      }
    } else {
      formDiv = FormItemObj[type](props)
    }
    const isFn: boolean = !!shouldUpdate || !!dependencies
    return (
      <Form.Item
        valuePropName={['switch', 'checkbox'].includes(type) ? 'checked' : undefined}
        name={!isFn ? name : undefined}
        label={!isFn ? label : undefined}
        noStyle={isFn ? true : false}
        shouldUpdate={shouldUpdate}
        dependencies={dependencies}
        {...antdFormItemArgesProps}
      >
        {formDiv}
      </Form.Item>
    )
  }
  return null
})
export default FormItemComponent

2.3 新建FormItemObj.tsx负责渲染具体表单元素

import type {
  CheckboxProps,
  DatePickerProps,
  RadioGroupProps,
  SelectProps,
  SwitchProps,
} from 'antd'
import { Button, Checkbox, DatePicker, Input, Radio, Select, Switch } from 'antd'
import { TextAreaProps } from 'antd/es/input/TextArea'
import React from 'react'
import InputSeletct from './CustFormItem/InputSeletct'
import type { FormItemType } from './index'
type RenderFn = (item: FormItemType) => React.ReactNode
const renderInput: RenderFn = (item) => <Input {...item.renderFormItemProps} />
const renderInputPassword: RenderFn = (item) => <Input.Password {...item.renderFormItemProps} />
const renderTextArea: RenderFn = (item) => (
  <Input.TextArea {...(item.renderFormItemProps as TextAreaProps)} />
)
const renderInputseletct: RenderFn = (item) => (
  <InputSeletct {...(item.renderFormItemProps as SelectProps)} />
)
const renderSelect: RenderFn = (item) => <Select {...(item.renderFormItemProps as SelectProps)} />
const renderDatePicker: RenderFn = (item) => (
  <DatePicker {...(item.renderFormItemProps as DatePickerProps)} />
)
const renderSwitch: RenderFn = (item) => <Switch {...(item.renderFormItemProps as SwitchProps)} />
const renderCheckbox: RenderFn = (item) => (
  <Checkbox {...(item.renderFormItemProps as CheckboxProps)} />
)
const renderRadio: RenderFn = (item) => (
  <Radio.Group {...(item.renderFormItemProps as RadioGroupProps)} />
)
const renderButton: RenderFn = (item) => <Button htmlType="button" {...item.renderFormItemProps} />
const renderSubmitButton: RenderFn = (item) => (
  <Button {...item.renderFormItemProps} type="primary" htmlType="submit" />
)
const FormItemObj = {
  input: renderInput,
  inputPassword: renderInputPassword,
  textArea: renderTextArea,
  select: renderSelect,
  inputseletct: renderInputseletct,
  datepicker: renderDatePicker,
  switch: renderSwitch,
  radio: renderRadio,
  checkbox: renderCheckbox,
  button: renderButton,
  submitBtn: renderSubmitButton,
}
export default FormItemObj

2.4 自定义表单元素InputSeletct.tsx

import { useControllableValue, useDebounce } from 'ahooks'
import type { SelectProps } from 'antd'
import { Select } from 'antd'
import { cloneDeep } from 'lodash-es'
import React, { useMemo, useState } from 'react'
type InputseletctProps = Omit<SelectProps, 'mode'>

const InputSeletct: React.FC<InputseletctProps> = ({
  value,
  onChange,
  labelInValue,
  options,
  onSearch,
  onClear,
  onBlur,
  ...agrs
}) => {
  const [currency, setCurrency] = useControllableValue<SelectProps['value']>({ value, onchange })
  const [inputVal, setInputVal] = useState<string>()
  const debouncedValue = useDebounce(inputVal, { wait: 100 })
  const TempOptions = useMemo(() => {
    const arr = options || []
    const text = debouncedValue ? debouncedValue.trim() : ''
    if (text === '') {
      return arr
    }
    const i = arr.findIndex((item) => item.label === text)
    if (i > -1) {
      return arr
    } else {
      const t = cloneDeep(arr)
      t.unshift({ label: text, value: text })
      return t
    }
  }, [debouncedValue, options])
  const onCurrencyChange: SelectProps['onChange'] = (newCurrency, option) => {
    setCurrency(newCurrency)
    onChange && onChange(newCurrency, option)
  }
  const inputSearch: SelectProps['onSearch'] = (value) => {
    setInputVal(value)
    onSearch && onSearch(value)
  }
  const onInputBlur = () => {
    const text = inputVal ? inputVal.trim() : ''
    if (text) {
      onCurrencyChange(labelInValue ? { label: text, value: text } : text, {
        label: text,
        value: text,
      })
    }
    onBlur && onBlur(value)
  }

  const clearInput = () => {
    setInputVal(undefined)
    onClear && onClear()
  }
  return (
    <Select
      {...agrs}
      value={value | currency}
      allowClear
      showSearch
      onSearch={inputSearch}
      onChange={onCurrencyChange}
      onBlur={onInputBlur}
      labelInValue={labelInValue}
      notFoundContent={null}
      options={TempOptions}
      onClear={clearInput}
    />
  )
}
export default InputSeletct

3.例子

import CustForms from '~/components/CustForms'
import styles from './index.module.scss'
const Login = () => {
  return (
    <div className={styles.login_page}>
      <CustForms
        formItems={[
          {
            name: 'usename',
            type: 'input',
            label: '用户',/*  */
            antdFormItemProps: {
              initialValue: '张三',
            },
          },
          {
            name: 'password',
            type: 'inputPassword',
            label: '密码',
            antdFormItemProps: {
              initialValue: '1111111',
            },
          },
        ]}
      />
    </div>
  )
}

export default Login

image.png