从零实现一个类似 Ant Design 的表单系统
项目概述
这是一个仿照 Ant Design 设计的 React 表单组件,核心功能包括:
- 实时验证与批量验证
- 统一的状态管理
- 灵活的组件属性注入
- 完整的 TypeScript 支持
架构设计
文件结构
bash
myForm/
├── form.tsx # 表单容器组件
├── item.tsx # 表单项组件
├── formContext.tsx # 状态共享Context
└── index.ts # 统一导出
组件关系
scss
App.tsx
└── MyForm (form.tsx)
├── FormContext.Provider
└── MyForm.Item (item.tsx)
└── 子组件 (input/checkbox等)
核心代码详解
1. FormContext - 状态管理中心
作用: 在 Form 和 Item 组件间共享数据和方法
typescript
// formContext.tsx
interface FormContextProps {
onValueChange?: (key: string, value: any) => void; // 字段值变更回调
validateRegister?: (name: string, cb: Function) => void; // 验证器注册方法
values?: Record<string, any>; // 表单数据对象
setValues?: (values: Record<string, any>) => void; // 批量设置表单数据
}
export const FormContext = createContext<FormContextProps>({});
设计要点:
- 使用 Context 避免 props 层层传递
- 提供数据读写和验证注册的统一接口
2. Form 组件 - 表单容器
核心职责: 状态管理、验证器收集、表单提交处理
状态定义
typescript
const Form = (props: FormProps) => {
// 表单数据状态
const [values, setValues] = useState(props.initialValues || {});
// 验证器映射 - 存储每个字段的验证函数
const validatorsMap = useRef(new Map<string, Function>());
核心方法实现
① 字段值变更处理
typescript
const onValueChange = (key: string, value: any) => {
setValues((prevValues) => ({ ...prevValues, [key]: value }));
};
- 当任意字段值改变时,更新对应的表单数据
- 使用函数式更新确保状态不可变性
② 验证器注册机制
typescript
const handleValidateRegister = (name: string, cb: Function) => {
validatorsMap.current.set(name, cb);
};
- 每个 Form.Item 将自己的验证函数注册到 Map 中
- 使用 useRef 避免重复创建 Map 对象
③ 表单提交核心逻辑
typescript
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
// 1. 收集所有注册的验证器
const validators = [];
for (const [, validator] of validatorsMap.current) {
if (typeof validator === "function") {
validators.push(validator);
}
}
// 2. 并行执行所有验证,传入最新的表单数据
Promise.all(validators.map((validator) => validator(values))).then(
(results) => {
// 3. 过滤出有错误的结果
const errorList = results.filter((item) => item !== "");
// 4. 根据验证结果执行对应回调
if (errorList.length) {
props.onFinishFailed?.(errorList); // 有错误,执行失败回调
} else {
props.onFinish?.(values); // 无错误,执行成功回调
}
}
);
};
提交流程分析:
- 阻止表单默认提交行为
- 从 validatorsMap 中收集所有验证函数
- 使用 Promise.all 并行执行验证(性能优化)
- 汇总验证结果,决定成功或失败回调
Context 数据提供
typescript
return (
<FormContext.Provider
value={{
values, // 表单数据
setValues, // 数据设置函数
onValueChange, // 值变更回调
validateRegister: handleValidateRegister, // 验证器注册
}}
>
<form onSubmit={handleSubmit}>{props.children}</form>
</FormContext.Provider>
);
3. Item 组件 - 表单项核心
核心职责: 子组件增强、验证处理、状态同步
状态和Context获取
typescript
const Item = (props: ItemProps) => {
const [errorInfo, setErrorInfo] = useState<string | null>(null);
const [value, setValue] = useState<string | number | boolean>("");
const { onValueChange, validateRegister, values } = useContext(FormContext);
关键技术1:动态属性注入
问题: 不同类型的表单控件使用不同的属性名接收值
- input 使用
value
属性 - checkbox 使用
checked
属性
解决方案:
typescript
// 动态计算要注入的属性
const propsName: Record<string, any> = {};
if (props.valuePropName) {
propsName[props.valuePropName] = value; // 自定义属性名,如 checked
} else {
propsName.value = value; // 默认使用 value
}
关键技术2:子组件克隆与增强
核心思路: 使用 React.cloneElement 为任意子组件注入属性和事件
typescript
const elChildren = React.Children.toArray(props.children).length > 0
? React.cloneElement(props.children!, {
...propsName, // 注入值属性(value 或 checked)
onChange: (e: ChangeEvent<HTMLInputElement>) => {
const newValue = getValueFromEvent(e);
setValue(newValue); // 更新本地状态
onValueChange?.(props.name!, newValue); // 通知表单状态
handleValidate(newValue); // 实时验证
},
})
: null;
增强过程分析:
- 检查是否有子组件
- 使用 cloneElement 克隆子组件
- 注入值属性(根据 valuePropName 决定属性名)
- 注入 onChange 事件处理函数
关键技术3:事件值提取
问题: 不同类型的表单控件返回值的方式不同
typescript
const getValueFromEvent = (e: React.ChangeEvent<HTMLInputElement>) => {
const { target } = e;
if (target.type === "checkbox") {
return target.checked; // checkbox 返回 checked 状态
}
return target.value; // 其他控件返回 value
};
关键技术4:双重验证机制
实时验证 - 输入时触发
typescript
onChange: (e) => {
const newValue = getValueFromEvent(e);
setValue(newValue);
onValueChange?.(props.name!, newValue);
handleValidate(newValue); // 直接使用新输入的值进行验证
}
批量验证 - 提交时触发
typescript
useEffect(() => {
if (props.rules && props.rules.length) {
validateRegister?.(props.name!, (formValues) => {
// 提交时验证函数,使用表单传入的最新值
return handleValidate(formValues[props.name!]);
});
}
}, [props.rules, props.name]);
两种验证的区别:
- 实时验证:使用当前输入值,立即反馈
- 批量验证:使用表单完整数据,最终校验
验证函数核心实现
typescript
const handleValidate = (validateValue?: any): Promise<string> => {
return new Promise((resolve) => {
if (Array.isArray(props.rules) && props.rules.length) {
const validator = new Schema({ [props.name!]: props.rules });
// 智能值选择:优先使用传入值,其次使用表单值
const valueToValidate = validateValue !== undefined
? (typeof validateValue === "object"
? validateValue[props.name!] // 对象类型,取对应字段
: validateValue) // 基本类型,直接使用
: values?.[props.name!]; // 兜底使用表单值
validator.validate({ [props.name!]: valueToValidate }, (errors) => {
setErrorInfo(errors?.[0]?.message ?? ""); // 设置错误信息
resolve(errors?.[0]?.message ?? ""); // 返回验证结果
});
} else {
resolve(""); // 无验证规则,直接通过
}
});
};
状态同步机制
typescript
useEffect(() => {
if (value !== values?.[props.name!]) {
setValue(values?.[props.name!]); // 同步表单数据到本地状态
}
}, [values, props.name, value, props.valuePropName]);
4. 统一导出策略
目标: 实现 <MyForm><MyForm.Item /></MyForm>
的 API 设计
typescript
// index.ts
import Form from "./form";
import Item from "./item";
type InternalFormType = typeof Form;
interface FormInterface extends InternalFormType {
Item: typeof Item;
}
const MyForm = Form as FormInterface;
MyForm.Item = Item;
export default MyForm;
完整数据流程
用户输入流程
lua
1. 用户在 input 中输入 "hello"
2. 触发 onChange 事件
3. getValueFromEvent 提取值 "hello"
4. setValue("hello") 更新本地状态
5. onValueChange("name", "hello") 通知表单更新
6. handleValidate("hello") 实时验证
7. 显示验证结果(成功/失败)
表单提交流程
markdown
1. 用户点击提交按钮
2. handleSubmit 阻止默认行为
3. 从 validatorsMap 收集所有验证器
4. Promise.all 并行执行验证,传入完整表单数据
5. 每个验证器调用 handleValidate(formValues[fieldName])
6. 汇总所有验证结果
7. 根据结果执行 onFinish 或 onFinishFailed
关键问题与解决方案
问题1:React 状态更新的异步性
现象: 输入第一个字符时立即显示验证错误
根本原因:
typescript
// 错误的做法
onChange: (e) => {
const newValue = getValueFromEvent(e);
setValue(newValue); // 异步更新,不会立即生效
onValueChange(props.name!, newValue); // 异步更新,不会立即生效
handleValidate(values); // 使用的还是旧的 values
}
正确解决:
typescript
// 正确的做法
onChange: (e) => {
const newValue = getValueFromEvent(e);
setValue(newValue);
onValueChange(props.name!, newValue);
handleValidate(newValue); // 直接使用新输入的值
}
核心原理: React 状态更新是异步的,在同一个事件处理函数中,状态值还是旧的。必须直接使用新值进行验证。
问题2:验证时机的统一处理
挑战: 实时验证和提交验证需要使用不同的数据源
解决策略:
typescript
const handleValidate = (validateValue?: any): Promise<string> => {
// 智能值选择策略
const valueToValidate = validateValue !== undefined
? (typeof validateValue === "object" ? validateValue[props.name!] : validateValue)
: values?.[props.name!];
// 使用统一的验证逻辑
};
- 输入时:传入新输入值
handleValidate(newValue)
- 提交时:传入表单对象
handleValidate(formValues)
- 兜底:使用 Context 中的值
问题3:TypeScript 类型安全
类型冲突: 验证函数需要同时支持单个值和对象参数
解决方案:
typescript
// 使用 any 类型,在函数内部进行类型判断
const handleValidate = (validateValue?: any): Promise<string> => {
const valueToValidate = validateValue !== undefined
? (typeof validateValue === "object"
? validateValue[props.name!] // 对象类型处理
: validateValue) // 基本类型处理
: values?.[props.name!]; // 兜底处理
};
性能优化要点
1. useRef 优化
typescript
// 使用 useRef 存储验证器,避免重复创建
const validatorsMap = useRef(new Map<string, Function>());
2. 并行验证
typescript
// 使用 Promise.all 并行执行所有验证,而不是串行
Promise.all(validators.map((validator) => validator(values)))
3. 精确依赖
typescript
// useEffect 使用精确的依赖数组,避免不必要的重新执行
}, [values, props.name, value, props.valuePropName]);
4. 函数式更新
typescript
// 使用函数式更新确保状态更新的正确性
setValues((prevValues) => ({ ...prevValues, [key]: value }));
使用示例
typescript
<MyForm
initialValues={{ name: "", age: 18 }}
onFinish={(values) => console.log('成功:', values)}
onFinishFailed={(errors) => console.log('失败:', errors)}
>
<MyForm.Item
name="name"
label="用户名"
rules={[
{ required: true, message: "请输入用户名!" },
{ max: 6, message: "长度不能大于 6" }
]}
>
<input />
</MyForm.Item>
<MyForm.Item name="agree" valuePropName="checked">
<input type="checkbox" /> 同意协议
</MyForm.Item>
<MyForm.Item>
<button type="submit">提交</button>
</MyForm.Item>
</MyForm>
核心技术总结
这个表单组件体现了以下核心技术:
- Context + Hook 状态管理 - 跨组件数据共享
- React.cloneElement 组件增强 - 动态注入属性和事件
- async-validator 验证集成 - 强大的验证能力
- Promise 并发处理 - 性能优化的验证策略
- TypeScript 类型安全 - 完整的类型系统
- React 状态异步处理 - 正确处理状态更新时机