🚀 在你的项目中,你是如何使用TypeScript进行表单验证的?
欢迎来到我的博客文章!所有文章都是满满的前端干货,文章简明扼要。
✅ 核心方案:Zod + 类型推导 + 表单库集成
1. 用 Zod 定义表单 Schema(同时获得类型和校验逻辑)
import { z } from 'zod';
export const MemberSchema = z.object({
key: z.string().min(1, '唯一标识不能为空'),
workId: z.string().regex(/^\d+$/, '工号必须为数字'),
name: z.string().min(2, '姓名至少两个字符'),
department: z.string().min(1, '部门不能为空'),
});
export const FormSchema = z.object({
members: z.array(MemberSchema).min(1, '至少添加一名成员'),
});
export type FormValues = z.infer<typeof FormSchema>;
✅ 优势
避免手动维护 interface,类型与校验规则始终保持一致。
2. 在 ProForm 中使用
ProForm 本身基于 Ant Design Form,支持自定义校验规则。我们将 Zod 规则包装成 AntD 的 Rule:
工具函数:将 Zod 转换为 Ant Design 规则
import { Rule } from 'antd/es/form';
import { ZodTypeAny } from 'zod';
export const zodRule = (schema: ZodTypeAny): Rule => ({
validator(_, value) {
if (value === undefined || value === null) return Promise.resolve();
try {
schema.parse(value);
return Promise.resolve();
} catch (err: any) {
return Promise.reject(new Error(err.errors[0].message));
}
},
});
在表单项中使用
import { ProFormList, ProFormText } from '@ant-design/pro-form';
import { MemberSchema } from '@/schemas/member';
import { zodRule } from '@/utils/zodToAntd';
<ProFormList name="members">
<ProFormText
name="workId"
label="工号"
rules={[zodRule(MemberSchema.shape.workId)]}
/>
<ProFormText
name="name"
label="姓名"
rules={[zodRule(MemberSchema.shape.name)]}
/>
</ProFormList>
3. 提交时二次校验(防御性编程)
即使前端有校验,提交时仍用 Zod 做最终检查(防止绕过 UI 直接调用逻辑)
const onFinish = async (values: unknown) => {
const result = FormSchema.safeParse(values);
if (!result.success) {
const firstError = result.error.issues[0];
message.error(firstError.message);
return;
}
await api.submitMembers(result.data.members);
};
完整示例:成员管理表单
1. Schema 定义(schemas/member.ts)
import { z } from 'zod';
export const MemberSchema = z.object({
key: z.string().min(1, '唯一标识不能为空'),
workId: z.string().regex(/^\d+$/, '工号必须为数字'),
name: z.string().min(2, '姓名至少两个字符'),
department: z.string().min(1, '部门不能为空'),
});
export const FormSchema = z.object({
members: z.array(MemberSchema).min(1, '至少添加一名成员'),
});
export type Member = z.infer<typeof MemberSchema>;
export type FormValues = z.infer<typeof FormSchema>;
2. 工具函数(utils/zodToAntd.ts)
import { Rule } from 'antd/es/form';
import { ZodTypeAny } from 'zod';
export const zodRule = (schema: ZodTypeAny): Rule => ({
validator(_, value) {
if (value === undefined || value === null) {
return Promise.resolve();
}
try {
schema.parse(value);
return Promise.resolve();
} catch (err: any) {
return Promise.reject(new Error(err.errors[0].message));
}
},
});
3. 表单组件(components/MemberForm.tsx)
import { ProForm, ProFormList, ProFormText } from '@ant-design/pro-form';
import { message } from 'antd';
import { MemberSchema, FormSchema, FormValues } from '@/schemas/member';
import { zodRule } from '@/utils/zodToAntd';
export default function MemberForm() {
const onFinish = async (values: unknown) => {
const result = FormSchema.safeParse(values);
if (!result.success) {
const firstError = result.error.issues[0];
message.error(firstError.message);
return;
}
const data: FormValues = result.data;
await api.submitMembers(data.members);
message.success('提交成功');
};
return (
<ProForm onFinish={onFinish}>
<ProFormList name="members">
<ProFormText
name="workId"
label="工号"
rules={[zodRule(MemberSchema.shape.workId)]}
/>
<ProFormText
name="name"
label="姓名"
rules={[zodRule(MemberSchema.shape.name)]}
/>
<ProFormText
name="department"
label="部门"
rules={[zodRule(MemberSchema.shape.department)]}
/>
</ProFormList>
</ProForm>
);
}
核心优势总结
✅ 类型安全
- Schema 定义即类型定义,避免手动维护 interface
- 自动推导类型,编译时检查
- 提交时的数据是完全类型安全的
✅ 运行时校验
- Zod 提供强大的运行时校验能力
- 支持正则、自定义规则、异步校验
- 错误信息可自定义,用户体验好
✅ 防御性编程
- 表单项级别校验(用户输入时)
- 提交时二次校验(防止绕过 UI)
- 后端接收前再次校验(完整防护)
✅ 开发体验
- 一处定义,多处使用
- IDE 自动补全和类型提示
- 重构时编译器会提示所有需要修改的地方