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

解决TypeScript循环递归类型的过深实例化错误

TypeScript循环嵌套Include参数类型优化方案

问题背景

我最近在学习TypeScript高级类型,想给REST API的包含参数做精确类型定义——获取实体时可传入嵌套的包含项,部分包含项还能继续嵌套子包含项。

之前用的简单接口定义:

interface IncludeParam {
  param: string
  include?: IncludeParam[]
}

后来设计了带泛型的精细类型,但遇到循环引用问题:

type IncludeParam<
  Params extends string = string,
  NestedIncludes extends { [param in Params]?: IncludeParam } = { [param in Params]: IncludeParam },
> = {
  [Param in Params]: {
    param: Param
    include?: NestedIncludes[Param] extends IncludeParam ? NestedIncludes[Param][] : never[]
  }
}[Params]

type UserInclude = IncludeParam<'posts' | 'payment' | 'awards', {
  posts: PostInclude
  payment: PaymentInclude
}>

type PostInclude = IncludeParam<'user' | 'images', {
  user: UserInclude
}>

type PaymentInclude = IncludeParam<'provider', {}>

// 合法示例
const validUserIncludes: UserInclude[] = [
  { param: 'payment', include: [{ param: 'provider' }] },
  { param: 'posts', include: [{ param: 'images' }] },
  { param: 'awards' },
]

// 非法示例(预期报错)
const invalidUserIncludes: UserInclude[] = [
  // @ts-expect-error
  { param: 'foo' },
  // @ts-expect-error
  { param: 'posts', include: [{ param: 'provider' }]},
  // @ts-expect-error
  { param: 'awards', include: [{ param: 'foo' }] },
  // @ts-expect-error
  {
    param: 'posts',
    include: [
      { param: 'images' },
      { param: 'user', include: [{ param: 'foo' }] }
    ]
  },
]

当前遇到的错误:

Type instantiation is excessively deep and possibly infinite

在TS 4.6.4版本中无此错误,但循环嵌套时类型检查失效,需要优化类型定义以支持循环引用,或在循环时放宽对应嵌套的检查。


优化方案

方案1:递归条件类型+延迟实例化(推荐)

核心思路是调整类型逻辑,让TypeScript能识别循环边界,避免无限递归实例化:

type IncludeParam<
  Params extends string,
  Nested extends Record<string, unknown> = {}
> = {
  [P in Params]: {
    param: P;
    // 仅当当前参数在Nested中有定义时,才允许嵌套include
    include?: P extends keyof Nested 
      ? Nested[P] extends IncludeParam<any, any> 
        ? Nested[P][] 
        : never[] 
      : never[];
  };
}[Params];

// 循环引用的类型定义保持不变,TS会自动处理边界
type UserInclude = IncludeParam<'posts' | 'payment' | 'awards', {
  posts: PostInclude;
  payment: PaymentInclude;
}>;

type PostInclude = IncludeParam<'user' | 'images', {
  user: UserInclude;
}>;

type PaymentInclude = IncludeParam<'provider', {}>;

这个修改让TS在处理循环引用时,不会无限递归实例化类型,同时保留外层参数的合法性校验,合法示例无报错,非法示例的错误能被正确捕获。

方案2:循环时放宽嵌套检查

如果希望在循环嵌套层级跳过严格的类型检查,可以用unknown代替循环的嵌套类型:

type IncludeParam<
  Params extends string,
  Nested extends Record<string, unknown> = {}
> = {
  [P in Params]: {
    param: P;
    include?: P extends keyof Nested 
      ? // 检测到循环引用时,用unknown[]放宽检查
        Nested[P] extends IncludeParam<infer _, infer N> 
        ? N extends { [key: string]: IncludeParam<any, any> } 
          ? unknown[] 
          : Nested[P][] 
        : never[] 
      : never[];
  };
}[Params];

该方案在遇到循环嵌套时,允许任意合法的IncludeParam结构,既避免了无限递归错误,又保证了外层参数的正确性。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 16:10:30