在React Hook Form中使用Zod时refine()/superrefine()失效求助
解决Zod superRefine自定义提示不生效的问题
你的问题核心在于Zod的校验执行顺序:基础类型校验会优先于superRefine逻辑执行。当fieldCodeToConcat传入数组时,你定义的z.object().optional()会先触发类型校验错误,直接返回默认的"Expected object, received array",根本不会进入superRefine的自定义校验逻辑,所以自定义提示永远无法触发。
解决方案:放宽基础类型校验,将逻辑移至superRefine
通过z.union放宽fieldCodeToConcat的基础类型限制,允许数组、对象或undefined通过,再在superRefine中集中处理所有校验规则,确保自定义提示能正常触发:
const defineFieldLineSchema = z.object({ accountingFieldCode: z.object({ fieldCode: z.string(), fieldCodeName: z.string(), description: z.string().nullable(), }), accountingOutputName: z.string().min(1, "Field is required"), defaultValueApplicable: z.string(), defaultValue: z.string().optional(), prefixValue: z.string().optional(), suffixValue: z.string().optional(), fieldCodeConcatRequired: z.enum(["Y", "N"]), concatCharacter: z.string(), // 放宽基础类型:允许object、数组、undefined通过 fieldCodeToConcat: z.union([ z.object({ fieldCode: z.string(), fieldCodeName: z.string(), description: z.string().nullable(), }), z.array(z.any()), z.undefined() ]), maxDataLength: z.number(), }).superRefine((data, ctx) => { // 校验1:当fieldCodeConcatRequired为Y时,必须提供fieldCodeToConcat if (data.fieldCodeConcatRequired === "Y" && !data.fieldCodeToConcat) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "fieldCodeToConcat is required when fieldCodeConcatRequired is 'Y'", path: ["fieldCodeToConcat"], }); } // 校验2:fieldCodeToConcat不能是数组 if (Array.isArray(data.fieldCodeToConcat)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "fieldCodeToConcat must be an object, not an array", path: ["fieldCodeToConcat"], }); } // 可选:额外校验fieldCodeToConcat的结构(当它是对象时) if (data.fieldCodeToConcat && !Array.isArray(data.fieldCodeToConcat)) { const objSchema = z.object({ fieldCode: z.string(), fieldCodeName: z.string(), description: z.string().nullable(), }); const parseResult = objSchema.safeParse(data.fieldCodeToConcat); if (!parseResult.success) { parseResult.error.issues.forEach(issue => { ctx.addIssue({ ...issue, path: ["fieldCodeToConcat", ...issue.path] }); }); } } })
备选方案:用preprocess拦截值
如果不想放宽基础类型,可以用z.preprocess拦截传入值,避免基础校验提前报错:
const defineFieldLineSchema = z.object({ // 其他字段保持不变 fieldCodeToConcat: z.preprocess((val) => { // 保留数组值,让superRefine处理校验 if (Array.isArray(val)) return val; return val; }, z.object({ fieldCode: z.string(), fieldCodeName: z.string(), description: z.string().nullable(), }).optional()), // 其他字段保持不变 }).superRefine((data, ctx) => { // 执行相同的自定义校验逻辑 if (data.fieldCodeConcatRequired === "Y" && !data.fieldCodeToConcat) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "fieldCodeToConcat is required when fieldCodeConcatRequired is 'Y'", path: ["fieldCodeToConcat"], }); } if (Array.isArray(data.fieldCodeToConcat)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "fieldCodeToConcat must be an object, not an array", path: ["fieldCodeToConcat"], }); } })
额外注意事项
确保在React Hook Form中正确配置Zod解析器:
import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; const form = useForm({ resolver: zodResolver(defineFieldLineSchema), // 其他配置 });
内容的提问来源于stack exchange,提问作者Shiladitya Thakur
相关产品推荐
相关产品推荐

