表单流水线
用 schema 验证、可访问输入和渐进增强构建表单。
为什么
表单是用户给你数据的主要方式——注册、结账、设置——坏表单体验直接损转化。zod + react-hook-form 表单流水线给你:schema 驱动验证(客户端服务端单一事实源)、默认可访问(label、aria、错误关联)、渐进增强(无 JS 可用、blur 验证、enter 提交)。没有它,表单是 useState 堆 + 手动验证 + 临时错误消息,不一致、不可访问、不匹配服务端验证。schema 驱动意味着加字段是一行 schema 变更非 5 文件编辑。
何时使用
应用有 ≥1 用户输入表单时。搭流水线约 1-2 小时。只读应用可跳过。
如何做
1. 用 zod 定义表单 schema——单一事实源
- const schema = z.object({ email: z.string().email(), password: z.string().min(8) })
- 跨字段验证加 refine()(密码确认匹配)
2. 用 react-hook-form 加 zodResolver 把 schema 接表单
- const form = useForm({ resolver: zodResolver(schema) })
- register("email") 给你 onChange、onBlur、ref 和错误状态
3. 建可访问输入组件:label + input + 错误消息
- <label htmlFor="email"> + <input id="email" aria-invalid={!!error} aria-describedby="email-error" />
- 错误:<p id="email-error" role="alert">{error.message}</p>
4. 用同 schema 重验证的 server action 提交
- 永不仅信客户端验证——服务端是权威
- 返回表单可显示的类型化错误(字段→消息)
5. 加渐进增强:无 JS 表单可用
- 用原生 <form action={serverAction}> 无 JS 提交
- JS 增强内联验证,但回退是正常 POST示例
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
const schema = z.object({
email: z.string().email("输入有效邮箱"),
password: z.string().min(8, "至少 8 字符"),
});
type FormData = z.infer<typeof schema>;
export function SignupForm() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({ resolver: zodResolver(schema) });
return (
<form action={signupAction} onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="email">邮箱</label>
<input id="email" type="email" {...register("email")} aria-invalid={!!errors.email} aria-describedby="email-err" />
{errors.email && <p id="email-err" role="alert">{errors.email.message}</p>}
<button type="submit">注册</button>
</form>
);
}常见问题
- ⚠仅提交时验证,用户填全表单才知第一字段错。
- ⚠label 未关联 input(无 htmlFor/id),屏幕阅读器报'编辑文本'无上下文。
- ⚠客户端服务端验证消息不同,用户修客户端错得不同服务端错。
- ⚠验证时禁提交按钮,JS 禁用用户永不提交。
最佳实践
- ✓blur 时验证(非每键)给即时反馈不烦人。
- ✓客户端服务端用同 zod schema,验证按构造一致。
- ✓清晰显示成功状态(绿勾、确认消息)——用户需知表单成了。
- ✓长表单分步带进度指示和每步验证。