📖 核心概念
- 受控组件与非受控组件
- React Hook Form:高性能表单
- Zod:schema验证
- 表单状态管理模式
- 动态表单与字段数组
- 表单UX最佳实践
💻 代码实现
React Hook Form + Zod
✅ tsx
import { useForm, useFieldArray, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
// Zod Schema定义
const userSchema = z.object({
name: z.string().min(2, '姓名至少2个字符').max(20, '姓名最多20个字符'),
email: z.string().email('请输入有效的邮箱地址'),
password: z.string()
.min(8, '密码至少8位')
.regex(/[A-Z]/, '密码需包含大写字母')
.regex(/[0-9]/, '密码需包含数字'),
confirmPassword: z.string(),
phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入有效的手机号'),
address: z.object({
province: z.string().min(1, '请选择省份'),
city: z.string().min(1, '请选择城市'),
detail: z.string().min(5, '详细地址至少5个字符'),
}),
skills: z.array(z.object({
name: z.string().min(1, '技能名称不能为空'),
level: z.enum(['beginner', 'intermediate', 'advanced']),
})).min(1, '至少添加一项技能'),
agree: z.literal(true, { message: '请同意用户协议' }),
}).refine(data => data.password === data.confirmPassword, {
message: '两次密码输入不一致',
path: ['confirmPassword'],
});
type UserFormData = z.infer<typeof userSchema>;
function RegistrationForm() {
const {
register, handleSubmit, control, formState: { errors, isSubmitting },
watch, reset, setError,
} = useForm<UserFormData>({
resolver: zodResolver(userSchema),
defaultValues: {
skills: [{ name: '', level: 'beginner' }],
},
});
const { fields, append, remove } = useFieldArray({ control, name: 'skills' });
const onSubmit = async (data: UserFormData) => {
try {
await api.register(data);
toast.success('注册成功!');
reset();
} catch (err) {
setError('root', { message: '注册失败,请重试' });
}
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="form">
<FormField label="姓名" error={errors.name}>
<input {...register('name')} placeholder="请输入姓名" />
</FormField>
<FormField label="邮箱" error={errors.email}>
<input {...register('email')} type="email" placeholder="your@email.com" />
</FormField>
<FormField label="密码" error={errors.password}>
<input {...register('password')} type="password" />
</FormField>
<FormField label="确认密码" error={errors.confirmPassword}>
<input {...register('confirmPassword')} type="password" />
</FormField>
{/* 嵌套对象 */}
<FormField label="地址">
<Controller name="address.province" control={control}
render={({ field }) => <ProvinceSelect {...field} />}
/>
<Controller name="address.city" control={control}
render={({ field }) => <CitySelect {...field} province={watch('address.province')} />}
/>
<input {...register('address.detail')} placeholder="详细地址" />
</FormField>
{/* 动态字段数组 */}
<div className="skills-section">
<h3>技能</h3>
{fields.map((field, index) => (
<div key={field.id} className="skill-row">
<input {...register(\`skills.\${index}.name\`)} placeholder="技能名称" />
<select {...register(\`skills.\${index}.level\`)}>
<option value="beginner">初级</option>
<option value="intermediate">中级</option>
<option value="advanced">高级</option>
</select>
<button type="button" onClick={() => remove(index)}>删除</button>
</div>
))}
<button type="button" onClick={() => append({ name: '', level: 'beginner' })}>
+ 添加技能
</button>
{errors.skills && <p className="error">{errors.skills.message}</p>}
</div>
<label className="checkbox">
<input type="checkbox" {...register('agree')} />
我同意用户协议
</label>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '提交中...' : '注册'}
</button>
</form>
);
}
🎯 练习任务
- 1 实现一个多步骤注册表单(向导模式)
- 2 创建一个动态表单构建器,根据JSON Schema生成表单
- 3 实现表单自动保存草稿功能
- 4 创建一个可复用的FormField组件,支持多种输入类型
🏆 成就解锁
表单工程师 — 掌握React表单开发与验证,构建用户友好的数据输入体验