ValidationError 默认消息模糊,应在 schema 定义阶段绑定业务化提示;需返回结构化错误而非扁平 message;Zod 自定义校验须用预设 code 才生效;i18n 应通过 message key + 响应时替换实现。

验证失败时 ValidationError 的默认消息太模糊
默认的 ValidationError 消息(比如 "Value must be a string" 或 "Expected number, received string")往往不贴合业务语境,前端展示时用户看不懂,排查时也难定位是哪个字段、什么规则出的问题。
关键不是“能不能改”,而是“在哪改最稳、最可控”。Zod、Yup、Joi 这类库都支持在 schema 定义阶段就绑定提示文本,而不是等抛错后再去解析错误对象——后者容易漏字段、难维护。
- Zod 用
.refine()或.superRefine()时,第二个参数传message字符串;基础类型校验直接用.string().min(1, "用户名不能为空") - Yup 用
.required("必填项")、.matches(/^[a-z]+$/, "只能小写字母"),消息写在验证方法调用里 - Joi 用
.messages({ "any.required": "邮箱是必填的" }),但注意键名必须查文档,拼错就无效
后端返回的错误结构和前端展示脱节
很多团队把校验失败的 message 直接塞进 HTTP 响应体顶层 message 字段,结果所有字段错误混成一句,前端无法按字段高亮或定位。真正该返回的是带路径的结构化错误。
比如 Zod 验证失败后调 error.format(),得到的是嵌套对象:{ name: [{ message: "长度不能少于2" }], email: [{ message: "邮箱格式不对" }] }。前端可直接用 Object.keys(errors) 映射表单字段。
- 别手动拼接
error.issues.map(i => i.message).join("; ")—— 丢掉字段上下文 - Node.js + Express 场景下,统一中间件捕获
ZodError,用format()转结构,再res.status(400).json({ errors }) - 如果用
error.flatten(),拿到的是{ formErrors: [], fieldErrors: { email: ["xxx"] } },更适合表单场景
transform() 或 superRefine() 中抛错不走自定义 message
在 Zod 里,如果在 .transform() 里 throw 新的 ZodError,或者在 .superRefine() 里用 ctx.addIssue({ code: "custom", message: "xxx" }),但没设 code,那最终错误里 message 可能被忽略或 fallback 到默认值。
根本原因是 Zod 对 code 字段有强约定:只有预设 code(如 "invalid_string"、"custom")才能触发 message 渲染逻辑。随便写个 "user_not_found",message 就不会出现在输出里。
- 自定义校验务必用
ctx.addIssue({ code: "custom", message: "手机号已注册" }) - 避免在
transform中 throw 字符串或普通 Error,要用new ZodError([...])或直接走superRefine - 检查
error.issues[0].code值是否在 Zod 错误码列表 内,不在就无效
国际化(i18n)时硬编码中文 message 导致维护困难
直接在 schema 里写 .email("邮箱格式不对"),后续加多语言就只能复制整套 schema,或者运行时动态 import 翻译函数——但 Zod schema 是静态定义的,没法在构造时注入 i18n 函数。
解法是把 message 抽成 key,在格式化错误时统一替换。比如用 error.format() 得到原始结构后,遍历 issues,对每个 message 做 t(issue.message)(前提是 message 是 key,如 "validation.email.invalid")。
- schema 定义阶段用 key 替代自然语言:
.email("validation.email.invalid") - 服务端响应前做一次 i18n 替换,不要让前端承担翻译逻辑
- 注意:Zod 不支持运行时切换 locale,所以 message key 必须是纯字符串,不能是
t("xxx")调用

















