Zod递归类型定义触发TS7022错误,如何正确实现?
Zod递归类型定义的TypeScript错误修复方案
问题描述
我在Zod中定义了递归类型(以LoadFindRoll为例):
import { z } from 'zod' const LoadFindLikeLinkBond = z.object({ link: z.string(), }) const LoadFindLikeBond = z.union([ z.string(), z.boolean(), z.null(), z.number(), ]) const LoadFindLike = z.object({ base: LoadFindLikeLinkBond, form: z.enum(['like']), head: z.union([LoadFindLikeLinkBond, LoadFindLikeBond]), }) const LoadFindRoll = z.object({ form: z.enum(["roll"]), list: z.lazy(() => z.array(LoadFindLink)), }); const LoadFindLink = z.lazy(() => z.union([LoadFindLike, LoadFindRoll]));
但最后两个类型触发了TypeScript错误:
'LoadFindLink'隐式拥有类型'any',因为它没有类型注解且在自身初始化器中直接或间接被引用.ts(7022)const LoadFindLink: any
我想实现Zod递归类型的正确结构,但不清楚如何适配当前场景。我的完整模块代码相互关联:
import { z } from 'zod' export const Load = z.object({ find: z.optional(z.lazy(() => LoadFind)), read: z.optional(z.lazy(() => LoadRead)), save: z.optional(z.lazy(() => LoadSave)), task: z.optional(z.string()), }) export const LoadFind = z.union([ z.lazy(() => LoadFindLink), z.array(z.lazy(() => LoadFindLink)), ]) export const LoadRead = z.record( z.union([z.boolean(), z.lazy(() => LoadReadLink)]), ) export const LoadSave = z.record( z.union( z.array(z.lazy(() => LoadSaveBase)), z.lazy(() => LoadSaveBase), ), ) export const LoadFindBind = z.object({ form: z.enum(['bind']), list: z.array(z.lazy(() => LoadFindLink)), }) export const LoadFindRoll = z.object({ form: z.enum(['roll']), list: z.lazy(() => z.array(LoadFindLink)), }) export const LoadFindTest = z.enum([ 'bond', 'base_link_mark', 'head_link_mark', 'base_mark', 'head_mark', 'base_text', 'miss_bond', 'have_bond', 'have_text', ]) export const LoadFindLike = z.object({ base: z.lazy(() => LoadFindLikeLinkBond), form: z.enum(['like']), head: z.union([ z.lazy(() => LoadFindLikeLinkBond), z.lazy(() => LoadFindLikeBond), ]), test: LoadFindTest, }) export const LoadFindLink = z.union([ z.lazy(() => LoadFindLike), z.lazy(() => LoadFindRoll), z.lazy(() => LoadFindBind), ]) export const LoadFindLikeBond = z.union([ z.string(), z.boolean(), z.null(), z.number(), ]) export const LoadFindLikeLinkBond = z.object({ link: z.string(), }) export const LoadReadLink = z.object({ find: z.optional(LoadFind), read: LoadRead, }) export const LoadReadLink = z.object({ find: z.optional(LoadFind), read: LoadRead, }) export const LoadSaveBase = z.object({ find: z.optional(z.lazy(() => LoadFind)), read: z.optional(z.lazy(() => LoadRead)), save: z.optional(z.lazy(() => LoadSave)), task: z.optional(z.string()), }) export const LoadSort = z.object({ name: z.string(), tilt: z.enum(['+', '-']), })
请问如何在Zod中正确定义这类递归类型?
解决方案
核心问题分析
出现TS7022错误的原因是:TypeScript无法在循环引用的变量初始化时推断出正确类型,必须通过提前声明类型注解+z.lazy()延迟加载的组合打破循环引用。
具体修复步骤
- 提前声明递归类型的TS类型别名:让TypeScript提前知晓类型结构,避免隐式
any。 - 全量使用
z.lazy()包裹递归引用:所有涉及循环依赖的类型引用,都要用z.lazy(() => 目标类型)包裹,确保初始化时不会出现未定义的情况。 - 调整变量声明顺序:把无递归依赖的基础类型(如
LoadFindLikeBond、LoadFindLikeLinkBond)放在递归类型之前声明。
修正后的完整代码
import { z } from 'zod' // 提前声明递归类型的TS类型别名,辅助TypeScript类型推断 type LoadFindLinkType = z.infer<typeof LoadFindLink> type LoadFindType = z.infer<typeof LoadFind> type LoadReadLinkType = z.infer<typeof LoadReadLink> type LoadSaveBaseType = z.infer<typeof LoadSaveBase> // 无递归依赖的基础类型优先声明 export const LoadFindLikeBond = z.union([ z.string(), z.boolean(), z.null(), z.number(), ]) export const LoadFindLikeLinkBond = z.object({ link: z.string(), }) export const LoadFindTest = z.enum([ 'bond', 'base_link_mark', 'head_link_mark', 'base_mark', 'head_mark', 'base_text', 'miss_bond', 'have_bond', 'have_text', ]) // 定义依赖基础类型的非核心递归类型 export const LoadFindLike = z.object({ base: LoadFindLikeLinkBond, form: z.enum(['like']), head: z.union([LoadFindLikeLinkBond, LoadFindLikeBond]), test: LoadFindTest, }) export const LoadFindBind = z.object({ form: z.enum(['bind']), list: z.array(z.lazy(() => LoadFindLink)), }) export const LoadFindRoll = z.object({ form: z.enum(['roll']), list: z.array(z.lazy(() => LoadFindLink)), }) // 核心递归联合类型,内部依赖用z.lazy包裹 export const LoadFindLink = z.union([ z.lazy(() => LoadFindLike), z.lazy(() => LoadFindRoll), z.lazy(() => LoadFindBind), ]) // 基于核心递归类型的扩展类型 export const LoadFind = z.union([ z.lazy(() => LoadFindLink), z.array(z.lazy(() => LoadFindLink)), ]) // LoadRead相关递归类型 export const LoadReadLink = z.object({ find: z.optional(z.lazy(() => LoadFind)), read: z.lazy(() => LoadRead), }) export const LoadRead = z.record( z.union([z.boolean(), z.lazy(() => LoadReadLink)]), ) // LoadSave相关递归类型 export const LoadSaveBase = z.object({ find: z.optional(z.lazy(() => LoadFind)), read: z.optional(z.lazy(() => LoadRead)), save: z.optional(z.lazy(() => LoadSave)), task: z.optional(z.string()), }) export const LoadSave = z.record( z.union([ z.array(z.lazy(() => LoadSaveBase)), z.lazy(() => LoadSaveBase), ]), ) // 顶层Load类型 export const Load = z.object({ find: z.optional(z.lazy(() => LoadFind)), read: z.optional(z.lazy(() => LoadRead)), save: z.optional(z.lazy(() => LoadSave)), task: z.optional(z.string()), }) export const LoadSort = z.object({ name: z.string(), tilt: z.enum(['+', '-']), })
额外注意点
- 代码中重复定义的
LoadReadLink已合并为一个,避免冗余。 - 所有递归引用必须用
z.lazy()包裹,哪怕是数组或对象内部的嵌套引用。 - 提前声明的TS类型别名仅用于辅助TypeScript推断,不会影响Zod的运行时验证逻辑。
内容的提问来源于stack exchange,提问作者Lance Pollard
相关产品推荐
相关产品推荐

