You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Zod+react-hook-form表单验证问题:至少填邮箱/手机号但校验失效

问题原因

你最后那个全局refine的逻辑写反了!Zod的refine规则是:返回false时才会触发错误提示,而你现在的代码是当邮箱和手机号都为空时返回true,这就相当于告诉Zod“这种情况是合法的”,自然不会触发校验错误。

修正方案

把全局refine的判断条件取反,改成当两者都为空时返回false,这样就能正确拦截空提交的情况:

const emailValidator = z.string().email()

export const contactFirstStepSchema = z
    .object({
        email: z
            .string()
            .refine(v => (v ? emailValidator.safeParse(v).success : true), "Invalid email"),
        phone: z.string().refine(value => {
            if (value) return isValidPhoneNumber(value, 'FR')
            return true
        }, "Invalid number"),
        description: z.string().optional()
    })
    .partial()
    .refine(({ email, phone }) => !( (email === undefined || email === '') && (phone === undefined || phone === '') ), {
        message: "At least one field should be provided (or both).",
        // 可选:指定错误关联到哪个字段,方便react-hook-form定位显示错误
        path: ['email'] // 也可以填['phone'],如果想让错误全局显示可以省略
    })
额外优化建议

你可以简化邮箱和手机号的校验写法,不用手动判断空值再校验,Zod本身支持optional()链式处理:

  • 邮箱直接写成z.string().email("Invalid email").optional(),空值会被视为合法,非空值才会校验格式
  • 手机号同理,用optional()标记空值合法,非空时再校验格式

优化后的代码示例:

export const contactFirstStepSchema = z
    .object({
        email: z.string().email("Invalid email").optional(),
        phone: z.string().refine(value => {
            if (!value) return true
            return isValidPhoneNumber(value, 'FR')
        }, "Invalid number").optional(),
        description: z.string().optional()
    })
    .refine(({ email, phone }) => !( !email && !phone ), {
        message: "At least one field should be provided (or both).",
        path: ['email']
    })

这里去掉了.partial(),因为每个字段都已标记为optional(),效果一致且代码更清晰。

内容的提问来源于stack exchange,提问作者Ahmed Zrouqui

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 16:52:15