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 {
formItems: CustFormItemProps[]
}
export type CustFormInstance = { form: FormInstance }
type RenderFormItemPropsType = Partial<{
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
antdFormItemProps?: Omit<FormItemProps, 'name' | 'label'> & {
shouldUpdateCallbackFn?: (form: FormInstance, custRenderObj: React.ReactNode) => React.ReactNode
}
label?: React.ReactNode
name?: string
renderFormItemProps?: RenderFormItemPropsType
children?: CustFormItemProps[]
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,
}))
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
