TypeScript递归类型实例化过深(TS2589)错误的优化方案咨询
问题描述
VS Code提示错误:
Type instantiation is excessively deep and possibly infinite.ts(2589)
我实现了一个递归类型NestedObjectValue,用于根据点分隔的路径字符串(如"grandparent.parent.child")获取嵌套对象的对应值,代码如下:
export type NestedObjectValue< T extends Record<string, any>, K extends Readonly<string> > = K extends `${infer F extends string}.${infer R extends string}` ? NestedObjectValue<T[F], R> : K extends keyof T ? T[K] : never
我的业务模型规模较大(包含600+记录),且存在无限递归结构,例如:
type Item = { "name": string, "children": Item[] }
请问:
- 这个
NestedObjectValue类型是否存在明显的低效问题? - 有哪些方案可以避免上述TypeScript错误?
回答
一、原类型的低效问题
原类型采用深度递归实现,每次拆分路径片段都会触发一次类型递归实例化。对于长路径(多层嵌套)或无限递归的业务模型(如Item),TypeScript会持续递归展开类型,很快就会触达类型系统的递归深度上限,这就是报错的直接原因。同时,这种深度递归的类型计算效率极低,大规模模型下会显著拖慢TypeScript的类型检查速度。
二、解决报错的方案
方案1:尾递归优化(TypeScript 4.5+)
TypeScript 4.5及以上版本支持尾递归优化,通过引入辅助参数累积当前处理的类型,让编译器可以优化递归深度:
export type NestedObjectValue< T extends Record<string, any>, K extends Readonly<string>, Current = T > = K extends `${infer F extends string}.${infer R extends string}` ? NestedObjectValue<T, R, Current[F]> : K extends keyof Current ? Current[K] : never
这里将当前遍历到的类型存储在Current参数中,每次递归仅更新Current而非重新从T开始,编译器可识别这种尾递归并优化,避免深度超限。
方案2:迭代式类型替代递归
将路径拆分为数组片段,再通过类似迭代的方式遍历类型,相比深度递归更稳定:
// 先拆分路径为字符串数组 type SplitPath<S extends string> = S extends `${infer F}.${infer R}` ? [F, ...SplitPath<R>] : [S]; // 用Reduce逻辑遍历路径 type PathReduce<T, Path extends string[]> = Path extends [infer F extends keyof T, ...infer Rest extends string[]] ? PathReduce<T[F], Rest> : T; export type NestedObjectValue< T extends Record<string, any>, K extends Readonly<string> > = PathReduce<T, SplitPath<K>>;
这种实现将路径拆分与类型遍历逻辑分离,减少了递归的深度依赖,计算效率更高,也更不容易触发深度限制。
方案3:限制路径最大深度
如果业务中路径的最大深度可预见(比如最多10层),可以直接编写固定层数的条件类型,完全避免递归:
export type NestedObjectValue< T extends Record<string, any>, K extends Readonly<string> > = // 可根据业务调整最大层数,超过则返回never或自定义处理 K extends `${infer F1}.${infer F2}.${infer F3}.${infer F4}.${infer F5}.${infer Rest}` ? never : K extends `${infer F1}.${infer F2}.${infer F3}.${infer F4}` ? T[F1][F2][F3][F4] : K extends `${infer F1}.${infer F2}.${infer F3}` ? T[F1][F2][F3] : K extends `${infer F1}.${infer F2}` ? T[F1][F2] : K extends keyof T ? T[K] : never;
该方案性能最优,但灵活性差,仅适用于路径深度固定的场景。
内容的提问来源于stack exchange,提问作者stackoverfloweth

