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

在TypeScript中创建递归结构时如何避免过度深层类型实例化?

解决TypeScript 4.9.4中递归类型的过度深层实例化问题

问题重现

以下是一个用于验证source是否符合schema结构的类型化函数示例,当在函数体内使用source时,会触发TS2589错误:“Type instantiation is excessively deep and possibly infinite”。

// TypeScript v4.9.4

type SchemaMeta = { type: SchemaAll, extra?: boolean };
type Schema1 = 1;
type Schema2 = 2;

type SchemaAll = Schema1 | Schema2 | SchemaMeta;

type Source<Schema extends SchemaAll> = (
    Schema extends Schema1 ? "String Option #1" :
    Schema extends Schema2 ? "String Option #2" :
    Schema extends SchemaMeta ? Source<Schema["type"]> :
    never
);

function validate<Schema extends SchemaAll>(schema: Schema, source: Source<Schema>): void {
    // 错误:TS2589: Type instantiation is excessively deep and possibly infinite.
    source;
}

// 正确的调用示例
validate({ type: 1 }, "String Option #1");
validate({ type: { type: 2, extra: false } }, "String Option #2");

// 正确的类型校验错误
validate(2, "String Option #1"); // Argument of type '"String Option #1"' is not assignable to parameter of type '"String Option #2"'.

该逻辑要求:

  • 当schema为1时,source必须是"String Option #1"
  • 当schema为2时,source必须是"String Option #2"
  • schema也可以是嵌套的SchemaMeta对象,其type字段支持自引用

原因分析

TypeScript 4.9.4对递归类型的实例化没有完善的尾递归优化机制。当函数体内需要解析Source<Schema>类型时,由于SchemaMeta的自引用特性,TypeScript会尝试无限递归展开Source<Schema["type"]>,直到达到类型实例化的深度上限,最终触发TS2589错误。

解决方案:添加递归深度限制

通过给Source类型引入一个可选的深度参数,手动限制递归展开的次数,避免无限递归。具体实现如下:

// TypeScript v4.9.4

type SchemaMeta = { type: SchemaAll, extra?: boolean };
type Schema1 = 1;
type Schema2 = 2;

type SchemaAll = Schema1 | Schema2 | SchemaMeta;

// 引入Depth参数限制递归深度,默认值10可根据需求调整
type Source<Schema extends SchemaAll, Depth extends number = 10> = 
    Depth extends 0 ? never : // 深度耗尽时终止递归
    Schema extends Schema1 ? "String Option #1" :
    Schema extends Schema2 ? "String Option #2" :
    Schema extends SchemaMeta ? Source<Schema["type"], [never, ...Array<unknown>][Depth]> : // 深度减1
    never;

function validate<Schema extends SchemaAll>(schema: Schema, source: Source<Schema>): void {
    // 现在可正常编译
    source;
}

// 原有测试用例依然正常工作
validate({ type: 1 }, "String Option #1");
validate({ type: { type: 2, extra: false } }, "String Option #2");

// 类型校验错误提示保持有效
validate(2, "String Option #1"); // Argument of type '"String Option #1"' is not assignable to parameter of type '"String Option #2"'.

方案说明

  • 利用Depth参数控制递归次数,默认设置为10(可根据实际嵌套深度需求调整)
  • 通过[never, ...Array<unknown>][Depth]实现深度减1的效果(数组长度技巧,将数字类型转换为长度减1的数组对应的数字类型)
  • 当深度耗尽(Depth extends 0)时返回never,终止递归展开

替代方案:函数体内规避类型检查

如果不需要在函数体内对source进行复杂的类型操作,也可以通过类型注释或断言临时规避检查,但这种方式不推荐用于需要严格类型校验的场景:

function validate<Schema extends SchemaAll>(schema: Schema, source: Source<Schema>): void {
    // @ts-ignore 临时跳过类型检查
    source;
}

或者使用类型断言:

function validate<Schema extends SchemaAll>(schema: Schema, source: Source<Schema>): void {
    const _source = source as unknown; // 转换为unknown避免递归解析
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 22:15:27