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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 04:12:52