如何基于React状态在新版Zod中实现条件字段校验?
新版Zod实现React状态驱动的条件必填校验
你需要基于一个取值为true/false的React状态,控制表单字段的必填性:当状态为true时目标字段为必填项,状态为false时则非必填。你之前找到的旧版Zod方案依赖ctx.parent属性,但新版Zod已移除该属性,这里提供两种适配新版的实现方式:
方案一:动态生成Schema(推荐)
直接根据外部状态动态生成对应的校验规则,这是最简洁高效的方式,无需额外处理状态同步:
import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; // 定义生成Schema的函数,接收外部状态参数 const buildFormSchema = (isTargetFieldRequired: boolean) => z.object({ targetField: isTargetFieldRequired ? z.string().trim().min(1, '此字段为必填项') : z.string().trim().optional(), otherField: z.string().optional() }); export default function ConditionalRequiredForm() { const [isTargetRequired, setIsTargetRequired] = useState(false); // 根据当前状态生成对应的Schema const formSchema = buildFormSchema(isTargetRequired); const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(formSchema) }); const handleFormSubmit = (data: z.infer<ReturnType<typeof buildFormSchema>>) => { console.log('提交数据:', data); }; return ( <form onSubmit={handleSubmit(handleFormSubmit)}> <div> <label> <input type="checkbox" checked={isTargetRequired} onChange={(e) => setIsTargetRequired(e.target.checked)} /> 标记目标字段为必填 </label> </div> <div> <input {...register('targetField')} placeholder="目标字段" /> {errors.targetField && <p style={{ color: 'red' }}>{errors.targetField.message}</p>} </div> <div> <input {...register('otherField')} placeholder="其他字段" /> </div> <button type="submit">提交表单</button> </form> ); }
方案二:合并外部状态到表单数据后校验
如果需要处理更复杂的多字段关联校验逻辑,可以把外部状态合并到表单数据中,在superRefine里直接读取状态进行校验:
import { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; // 定义包含外部状态映射字段的Schema const formSchema = z.object({ targetField: z.string().trim().optional(), otherField: z.string().optional(), isTargetRequired: z.boolean() }).superRefine((data, ctx) => { // 直接从data中获取状态,无需依赖ctx.parent if (data.isTargetRequired && !data.targetField) { ctx.addIssue({ path: ['targetField'], code: z.ZodIssueCode.custom, message: '启用必填时此字段不能为空' }); } }); export default function ComplexConditionalForm() { const [isTargetRequired, setIsTargetRequired] = useState(false); const { register, handleSubmit, formState: { errors }, setValue } = useForm({ resolver: zodResolver(formSchema), defaultValues: { isTargetRequired } }); // 同步外部状态到表单内部字段 useEffect(() => { setValue('isTargetRequired', isTargetRequired); }, [isTargetRequired, setValue]); const handleFormSubmit = (data: z.infer<typeof formSchema>) => { // 提交时剔除状态字段,只保留业务数据 const { isTargetRequired: _, ...submitData } = data; console.log('提交数据:', submitData); }; return ( <form onSubmit={handleSubmit(handleFormSubmit)}> <div> <label> <input type="checkbox" checked={isTargetRequired} onChange={(e) => setIsTargetRequired(e.target.checked)} /> 标记目标字段为必填 </label> </div> <div> <input {...register('targetField')} placeholder="目标字段" /> {errors.targetField && <p style={{ color: 'red' }}>{errors.targetField.message}</p>} </div> <div> <input {...register('otherField')} placeholder="其他字段" /> </div> <button type="submit">提交表单</button> </form> ); }
方案说明
- 方案一逻辑直观,性能更优,适合简单的单字段条件必填场景,状态变化时自动更新校验规则。
- 方案二适合复杂校验场景,比如需要同时结合多个外部状态或表单字段进行判断的情况,通过合并状态到表单数据实现统一校验。
内容的提问来源于stack exchange,提问作者Александр Кос
相关产品推荐
相关产品推荐

