在中后台系统开发中,表单是高频出现的交互组件。不同业务场景下,表单的字段、组件类型、校验规则往往差异很大,如果每个表单都重复编写渲染逻辑、校验逻辑和状态管理代码,会导致开发效率低下、代码冗余。本文将分享一个基于 React + Ant Design 实现的 useDynamicForm 自定义 hooks,帮助你快速构建可配置、高灵活度的动态表单。
一、核心特性
useDynamicForm hooks 封装了表单的核心能力,具备以下特性:
-
高可配置性:通过 Schema 定义表单项的字段名、标签、组件类型、校验规则等
-
支持多种组件:内置 Input、Select、Radio、Checkbox、DatePicker 等常用表单组件,支持扩展
-
动态交互能力:支持根据表单值动态控制字段显示 / 隐藏、组件属性动态变化
-
完整的 API 支持:提供表单值获取、设置、校验、重置、更新 Schema 等核心方法
-
灵活的布局配置:支持单列、双列、三列布局,适配不同页面空间需求
-
默认动作支持:内置提交 / 重置按钮,也可自定义实现
二、实现思路
1. 类型定义(type.ts)
首先通过 TypeScript 定义核心类型,确保类型安全和开发提示:
-
定义表单组件类型
FormComponentType -
定义表单项配置
FormItemConfig,包含字段名、标签、组件类型、校验规则、动态属性等 -
定义 hooks 配置
UseDynamicFormOptions,包含 Schema、布局配置、回调函数等 -
定义暴露给外部的 API 接口
FormApi
2. 核心逻辑(useDynamicForm.tsx)
(1)组件渲染函数
renderComponent 函数根据表单项配置的 component 类型,渲染对应的 Ant Design 组件,并处理组件的动态属性(支持函数形式的属性配置,可获取表单值和设置值方法)。
(2)动态显示控制
shouldShowItem 函数根据表单项的 show 配置(函数形式),结合当前表单值,判断该字段是否需要显示。
(3)表单状态管理
-
使用 Ant Design Form 的
useForm管理表单实例 -
通过状态
currentSchema管理当前表单配置,支持动态更新 -
通过
formValues状态同步表单值,用于动态控制字段显示和组件属性
(4)API 暴露
通过 useImperativeHandle 向外部暴露表单核心操作方法,包括:
-
获取 / 设置表单值(
getValues/setValues/setFieldValue) -
表单校验(
validate/validateField) -
表单重置(
reset) -
动态更新表单配置(
updateFormSchema)
3. 使用示例(MyForm.tsx)
通过 Schema 定义表单项,调用 useDynamicForm hooks 获取表单组件和 API,即可快速实现表单功能,支持自定义提交、重置、动态更新等操作。
三、完整代码实现

1. 类型定义文件(type.ts)
TypeScript
import type { FormRule, FormInstance } from "antd";
// 定义所有可能的表单组件类型
export type FormComponentType =
| "Input"
| "Select"
| "Radio"
| "Checkbox"
| "Switch"
| "ProSelect"
| "DatePicker"
| "TextArea";
// 定义表单项的配置
export interface FormItemConfig {
/** 字段名,对应表单值的 key */
name: string;
/** 表单项的标签 */
label: string | null;
/** 组件类型 */
component: FormComponentType;
/** 表单项的规则 */
rules?: FormRule[];
// 组件属性,支持函数形式(可获取表单值和设置值方法)
componentProps?:
| Record<string, any>
| ((args: {
formValues: Record<string, any>;
setFieldValue: (name: string, value: any) => void;
getFormValues: () => Record<string, any>;
}) => Record<string, any>);
// 是否禁用
disabled?: boolean;
// 占位符
placeholder?: string;
// 默认值
defaultValue?: any;
// 布局占比(对应 Ant Design Col 的 span)
span?: number;
// 条件显示配置:根据表单值判断是否显示
show?: (values: Record<string, any>) => boolean;
}
export interface UseDynamicFormOptions {
/** 表单项配置数组 */
schema: FormItemConfig[];
/** 是否显示默认的提交和重置按钮 */
showDefaultActions?: boolean;
/** 表单通用配置 */
commonConfig?: {
layout: "horizontal" | "vertical" | "inline";
/** 标签后是否显示冒号 */
colon?: boolean;
labelCol?: object;
wrapperCol?: object;
column?: 1 | 2 | 3;
};
/** 标签布局配置 */
labelCol?: object;
/** 输入框布局配置 */
wrapperCol?: object;
/** 提交按钮的文本 */
submitButtonText?: string;
/** 重置按钮的文本 */
resetButtonText?: string;
/** 提交回调函数 */
onSubmit?: (arg?: any) => void;
}
// 暴露给外部的表单 API
export interface FormApi {
/** Ant Design 的 Form 实例,提供底层 API */
formInstance: FormInstance;
/** 获取所有字段值 */
getValues: () => Record<string, any>;
/** 设置字段值(批量) */
setValues: (values: Record<string, any>) => void;
/** 设置单个字段值 */
setFieldValue: (name: string, value: any) => void;
/** 校验表单 */
validate: () => Promise<Record<string, any>>;
/** 校验单个字段 */
validateField: (name: string) => Promise<void>;
/** 重置表单 */
reset: (fields?: string[]) => void;
/** 更新表单配置 */
updateFormSchema: (schema: FormItemConfig[]) => void;
/** 获取单个字段值 */
getFieldValue: (name: string) => any;
/** 获取最新表单值 */
getFormValues: () => Record<string, any>;
/** 判断表单是否有修改 */
isDirty: () => boolean;
}
2. hooks 实现文件(useDynamicForm.tsx)
TypeScript
import { Button } from "antd";
import { getSelectData } from "@/utils/common";
import { HEALTHRISKASSESSMENT_RISK } from "@/concacts/valueEnum";
import { useDynamicForm } from "@/hooks/DynamicForm/useDynamicForm";
import type { FormItemConfig } from "@/hooks/DynamicForm/type";
import { SmileOutlined } from '@ant-design/icons';
function MyForm() {
const smileIcon = <SmileOutlined />;
// 1. 定义表单 Schema
const loginSchema: FormItemConfig[] = [
{
name: "username",
label: "用户名",
component: "Input",
placeholder: "请输入用户名",
rules: [{ required: true, message: "请输入用户名!" }],
},
{
name: "phone",
label: "手机号",
component: "Input",
placeholder: "请输入手机号",
rules: [
{ required: true, message: "请输入手机号!" },
{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号格式!" },
],
},
{
name: "price",
label: "金额单位",
component: "Select",
placeholder: "请选择金额单位",
rules: [{ required: true, message: "请选择金额单位!" }],
componentProps: {
options: [
{ value: "CNY", label: "人民币" },
{ value: "USD", label: "美元" },
{ value: "EUR", label: "欧元" },
],
showSearch: true,
onChange: (val, option) => {
console.log('金额单位变更:', val, option);
},
},
},
{
name: "furit",
label: "水果",
component: "Checkbox",
placeholder: "请选择水果",
rules: [{ required: true, message: "请选择水果!" }],
componentProps({ formValues, setFieldValue, getFormValues }) {
return {
options: [
{ label: "苹果", value: "Apple" },
{ label: "梨子", value: "Pear", disabled: true },
{ label: "橙子", value: "Orange" },
],
onChange: (val) => {
// 选择苹果时,自动设置风险等级和用户名、备注
if (val.includes('Apple')) {
setFieldValue('sickness', ['1']);
setFieldValue('username', `用户_${Date.now().toString().slice(-6)}`);
// 获取最新的表单值
const latestValues = getFormValues();
console.log('最新风险等级:', latestValues.sickness);
if (latestValues.sickness && latestValues.sickness.includes('1')) {
setFieldValue('remark', '您选择了苹果,已自动填写备注');
}
} else {
// 未选择苹果时,清空相关字段
setFieldValue('sickness', undefined);
setFieldValue('remark', undefined);
setFieldValue('username', `用户_${Date.now().toString().slice(-6)}`);
}
},
}
},
},
{
name: "sickness",
label: "风险等级",
component: "ProSelect",
placeholder: "请选择风险等级",
rules: [{ required: true, message: "请选择风险等级!" }],
componentProps({ setFieldValue }) {
return {
// 正常传递Select的文档options即可,这里无须复制
options: getSelectData(HEALTHRISKASSESSMENT_RISK),
showSearch: true,
onChange: (val) => {
console.log('风险等级变更:', val);
if (val.length === 0) {
setFieldValue('remark', undefined);
}
},
mode: "multiple",
}
}
},
{
name: "birthDate",
label: "出生日期",
component: "DatePicker",
placeholder: "请选择出生日期",
rules: [{ required: true, message: "请选择出生日期!" }],
componentProps: {
onChange: (val) => {
console.log('出生日期变更:', val);
},
disabledDate: (current) => {
// 禁用过去的日期
return current && current < new Date();
},
prefix: smileIcon,
style: { backgroundColor: 'lightgreen' }
}
},
{
name: "remark",
label: "备注",
component: "TextArea",
placeholder: "请输入备注",
rules: [{ required: true, message: "请输入备注!" }],
span: 24, // 占满一行
show(values) {
// 风险等级有选择时才显示备注字段
return values.sickness && values.sickness.length > 0;
},
},
];
// 2. 调用 hooks,获取表单组件和 API
const [Form, formApi] = useDynamicForm({
schema: loginSchema,
commonConfig: {
layout: "horizontal",
colon: true,
column: 2, // 双列布局
},
onSubmit: (val) => {
console.log('表单提交(默认按钮):', val);
},
});
// 自定义提交逻辑
const handleSubmit = async () => {
try {
// 表单校验
await formApi.validate();
// 获取表单值
const values = formApi.getValues();
console.log('表单提交(自定义按钮):', values);
// 这里可以添加接口请求等业务逻辑
} catch (error) {
console.log('表单校验失败:', error);
}
};
// 手动设置表单值(回显场景)
const setValues = () => {
formApi.setValues({
sickness: ['1'], // 风险等级设为第一个选项
});
};
// 动态更新表单配置
const updateFormSchema = () => {
formApi.updateFormSchema([
{
name: "username",
label: "用户名",
component: "Input",
placeholder: "请输入用户名",
rules: [{ required: true, message: "请输入用户名!" }],
},
{
name: "phone",
label: "手机号",
component: "Input",
placeholder: "请输入手机号",
rules: [{ required: true, message: "请输入手机号!" }],
},
]);
};
return (
<>
<div className="w-1/2">
{/* 渲染表单 */}
<Form />
</div>
{/* 自定义操作按钮 */}
<div className="flex mt-4">
<Button className="mr-4" type="primary" onClick={handleSubmit}>
自定义提交
</Button>
<Button className="mr-4" onClick={() => formApi.reset()}>
重置表单
</Button>
<Button className="mr-4" type="dashed" onClick={updateFormSchema}>
动态更新表单
</Button>
<Button type="dashed" onClick={setValues}>
手动设置值
</Button>
</div>
</>
);
}
export default MyForm;
3. 使用示例(MyForm.tsx)
TypeScript
import { Button } from "antd";
import { getSelectData } from "@/utils/common";
import { HEALTHRISKASSESSMENT_RISK } from "@/concacts/valueEnum";
import { useDynamicForm } from "@/hooks/DynamicForm/useDynamicForm";
import type { FormItemConfig } from "@/hooks/DynamicForm/type";
import { SmileOutlined } from '@ant-design/icons';
function MyForm() {
const smileIcon = <SmileOutlined />;
// 1. 定义表单 Schema
const loginSchema: FormItemConfig[] = [
{
name: "username",
label: "用户名",
component: "Input",
placeholder: "请输入用户名",
rules: [{ required: true, message: "请输入用户名!" }],
},
{
name: "phone",
label: "手机号",
component: "Input",
placeholder: "请输入手机号",
rules: [
{ required: true, message: "请输入手机号!" },
{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号格式!" },
],
},
{
name: "price",
label: "金额单位",
component: "Select",
placeholder: "请选择金额单位",
rules: [{ required: true, message: "请选择金额单位!" }],
componentProps: {
options: [
{ value: "CNY", label: "人民币" },
{ value: "USD", label: "美元" },
{ value: "EUR", label: "欧元" },
],
showSearch: true,
onChange: (val, option) => {
console.log('金额单位变更:', val, option);
},
},
},
{
name: "furit",
label: "水果",
component: "Checkbox",
placeholder: "请选择水果",
rules: [{ required: true, message: "请选择水果!" }],
componentProps({ formValues, setFieldValue, getFormValues }) {
return {
options: [
{ label: "苹果", value: "Apple" },
{ label: "梨子", value: "Pear", disabled: true },
{ label: "橙子", value: "Orange" },
],
onChange: (val) => {
// 选择苹果时,自动设置风险等级和用户名、备注
if (val.includes('Apple')) {
setFieldValue('sickness', ['1']);
setFieldValue('username', `用户_${Date.now().toString().slice(-6)}`);
// 获取最新的表单值
const latestValues = getFormValues();
console.log('最新风险等级:', latestValues.sickness);
if (latestValues.sickness && latestValues.sickness.includes('1')) {
setFieldValue('remark', '您选择了苹果,已自动填写备注');
}
} else {
// 未选择苹果时,清空相关字段
setFieldValue('sickness', undefined);
setFieldValue('remark', undefined);
setFieldValue('username', `用户_${Date.now().toString().slice(-6)}`);
}
},
}
},
},
{
name: "sickness",
label: "风险等级",
component: "ProSelect",
placeholder: "请选择风险等级",
rules: [{ required: true, message: "请选择风险等级!" }],
componentProps({ setFieldValue }) {
return {
options: getSelectData(HEALTHRISKASSESSMENT_RISK),
showSearch: true,
onChange: (val) => {
console.log('风险等级变更:', val);
if (val.length === 0) {
setFieldValue('remark', undefined);
}
},
mode: "multiple",
}
}
},
{
name: "birthDate",
label: "出生日期",
component: "DatePicker",
placeholder: "请选择出生日期",
rules: [{ required: true, message: "请选择出生日期!" }],
componentProps: {
onChange: (val) => {
console.log('出生日期变更:', val);
},
disabledDate: (current) => {
// 禁用过去的日期
return current && current < new Date();
},
prefix: smileIcon,
style: { backgroundColor: 'lightgreen' }
}
},
{
name: "remark",
label: "备注",
component: "TextArea",
placeholder: "请输入备注",
rules: [{ required: true, message: "请输入备注!" }],
span: 24, // 占满一行
show(values) {
// 风险等级有选择时才显示备注字段
return values.sickness && values.sickness.length > 0;
},
},
];
// 2. 调用 hooks,获取表单组件和 API
const [Form, formApi] = useDynamicForm({
schema: loginSchema,
commonConfig: {
layout: "horizontal",
colon: true,
column: 2, // 双列布局
},
onSubmit: (val) => {
console.log('表单提交(默认按钮):', val);
},
});
// 自定义提交逻辑
const handleSubmit = async () => {
try {
// 表单校验
await formApi.validate();
// 获取表单值
const values = formApi.getValues();
console.log('表单提交(自定义按钮):', values);
// 这里可以添加接口请求等业务逻辑
} catch (error) {
console.log('表单校验失败:', error);
}
};
// 手动设置表单值(回显场景)
const setValues = () => {
formApi.setValues({
sickness: ['1'], // 风险等级设为第一个选项
});
};
// 动态更新表单配置
const updateFormSchema = () => {
formApi.updateFormSchema([
{
name: "username",
label: "用户名",
component: "Input",
placeholder: "请输入用户名",
rules: [{ required: true, message: "请输入用户名!" }],
},
{
name: "phone",
label: "手机号",
component: "Input",
placeholder: "请输入手机号",
rules: [{ required: true, message: "请输入手机号!" }],
},
]);
};
return (
<>
<div className="w-1/2">
{/* 渲染表单 */}
<Form />
</div>
{/* 自定义操作按钮 */}
<div className="flex mt-4">
<Button className="mr-4" type="primary" onClick={handleSubmit}>
自定义提交
</Button>
<Button className="mr-4" onClick={() => formApi.reset()}>
重置表单
</Button>
<Button className="mr-4" type="dashed" onClick={updateFormSchema}>
动态更新表单
</Button>
<Button type="dashed" onClick={setValues}>
手动设置值
</Button>
</div>
</>
);
}
export default MyForm;
四、关键功能说明
1. 动态表单配置
通过 FormItemConfig 数组定义表单结构,支持以下配置项:
-
name:字段唯一标识(对应表单值的 key) -
label:字段标签 -
component:组件类型(如 Input、Select 等) -
rules:校验规则(Ant Design FormRule 格式) -
componentProps:组件属性(支持静态对象或动态函数) -
show:条件显示函数(根据表单值判断是否显示) -
defaultValue:默认值 -
span:布局占比
2. 动态交互能力
(1)组件属性动态变化
componentProps 支持函数形式,可获取以下参数:
-
formValues:当前表单值(状态同步值) -
setFieldValue:设置单个字段值的方法 -
getFormValues:获取最新表单值的方法(从表单实例中获取)
示例:选择水果时,自动设置风险等级、用户名和备注。
(2)字段条件显示
通过 show 配置项,传入一个函数,根据当前表单值判断字段是否显示。
示例:只有选择了风险等级,才显示备注字段。
3. 表单 API 用法
(1)获取表单值
TypeScript
const values = formApi.getValues(); // 获取所有字段值
const username = formApi.getFieldValue('username'); // 获取单个字段值
(2)设置表单值
TypeScript
formApi.setValues({ username: '张三', phone: '13800138000' }); // 批量设置
formApi.setFieldValue('username', '张三'); // 单个设置
(3)表单校验
TypeScript
try {
await formApi.validate(); // 校验所有字段
// 校验通过,处理提交逻辑
} catch (error) {
// 校验失败,处理错误
}
// 校验单个字段
await formApi.validateField('phone');
(4)重置表单
TypeScript
formApi.reset(); // 重置所有字段
formApi.reset(['username', 'phone']); // 重置指定字段
(5)动态更新表单配置
TypeScript
formApi.updateFormSchema(newSchema); // 传入新的 FormItemConfig 数组
五、扩展与优化建议
1. 组件扩展
如果需要支持更多组件(如 InputNumber、Upload 等),只需:
-
在
FormComponentType中添加组件类型 -
在
renderComponent函数中添加对应的组件渲染逻辑 -
支持自定义组件,如上述示例ProSelect
TypeScript
import { Select } from "antd";
import { ProSelectProps } from "./prop";
function ProSelect(props: ProSelectProps) {
const {
placeholder = "请选择",
showSearch = false,
allowClear = true,
mode,
onChange,
...selectProps
} = props;
const handleChange = (value: any, options: any) => {
// if (callback) {
// callback(value, options);
// }
onChange(value,options);
};
return (
<Select
placeholder={placeholder}
allowClear={allowClear}
showSearch={showSearch}
optionFilterProp="label"
onChange={handleChange}
{...selectProps}
mode={mode}
/>
);
}
export default ProSelect;
六、总结
useDynamicForm hooks 通过 Schema 配置化的方式,封装了表单的渲染、状态管理、校验等核心逻辑。