如何让Zod实现类似TypeScript接口与实现类的强制约束效果
如何让Zod实现类似TypeScript接口与实现类的强制约束效果
我明白你的需求:你想让NotificationDataSchema扮演一个类似TypeScript接口的角色,强制所有继承它的子Schema(比如NewFollowerSchema)必须显式定义data字段,而不是继承基础Schema里的空对象。默认情况下Zod的extend方法会合并字段,空的data会被保留,所以不会报错,但我们可以通过一个小技巧实现你要的强制约束。
核心解决方案:用z.never()标记必须被覆盖的字段
我们可以把基础Schema里的data字段定义为z.never()——这个类型表示该字段不可能有任何有效值,如果子Schema不重写它,无论是TypeScript类型检查还是Zod的运行时验证都会直接报错,完美模拟“接口强制实现”的效果。
具体代码实现
首先重构你的基础Schema:
import { z } from "zod"; // 基础Schema:用z.never()强制子Schema必须重写data字段 export const NotificationDataSchema = z.object({ type: z.string(), data: z.never(), // 关键:标记data为必须覆盖的字段 });
然后是正常实现的LikesMilestoneSchema(重写了data,完全没问题):
export const LikesMilestoneSchema = NotificationDataSchema.extend({ type: z.literal('LikesMilestone'), data: z.object({ count: z.number(), }), });
现在如果NewFollowerSchema不定义data,立刻会触发报错:
// ❌ 报错:data的类型是never,无法匹配任何实际数据,TS类型检查也会提示错误 export const NewFollowerSchema = NotificationDataSchema.extend({ type: z.literal('NewFollower'), // 没有定义data,此时data继承了z.never(),完全不可用 });
必须显式定义data才能通过验证:
// ✅ 正常工作:重写了data字段,符合约束 export const NewFollowerSchema = NotificationDataSchema.extend({ type: z.literal('NewFollower'), data: z.object({ followerId: z.string(), username: z.string(), // 这里可以根据需求定义任意字段 }), });
最后你的联合Schema和最终的NotificationSchema可以正常使用:
export const NotificationData = z.union([ LikesMilestoneSchema, NewFollowerSchema, ]); export const NotificationSchema = z.object({ id: z.string(), isRead: z.boolean(), date: z.date(), data: NotificationData, });
为什么这个方案有效?
- TypeScript层面约束:当子Schema不重写
data时,z.infer<typeof NewFollowerSchema>会得到data: never的类型,任何试图给这个字段赋值的代码都会触发TS类型错误,强制你必须定义data。 - 运行时验证约束:Zod的
never类型会拒绝任何输入值,所以如果有人不小心漏掉了data的定义,在验证数据时会直接抛出错误,避免无效数据流入前端。
替代方案:空联合类型z.union([])
如果你觉得z.never()不够直观,也可以用z.union([])来代替,它和z.never()的效果完全一致——空联合类型在TypeScript里就是never,同样能实现强制覆盖的约束。
内容来源于stack exchange
相关产品推荐
相关产品推荐

