解决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
相关产品推荐
相关产品推荐

