TypeScript类型比较栈深度溢出问题:原因及解决方法
「Excessive stack depth comparing types」错误原因及解决方法
错误原因
ReverseTuple递归无法正确终止:原定义中Rest的推断逻辑T extends [any, ...infer R extends [any, ...any[]]] ? R : never,在处理最后一个元素时无法将Rest收缩为终止状态,导致递归无限循环,超出TypeScript类型检查的栈深度限制。- 多层嵌套递归叠加:
compositor函数的类型约束嵌套了ReverseTuple<FunctionChainArray<ReverseTuple<T>>>,三层递归类型的叠加让类型计算复杂度指数级上升,直接触发栈溢出。 - 类型推断冗余复杂:
FunctionChainArray中使用Lookup工具类型和多余的类型断言,加上映射类型与递归的结合,进一步增加了类型检查器的计算负担,加剧栈深度问题。
解决方法
1. 修复ReverseTuple的递归终止逻辑
调整Rest的推断方式,确保递归到单个元素时停止,避免无限递归:
type ReverseTuple< T extends [any, ...any[]], NewArray extends any[] = [] > = T extends [infer F, ...infer R] ? R extends [] ? [F, ...NewArray] : ReverseTuple<R, [F, ...NewArray]> : NewArray;
2. 简化嵌套递归结构
减少compositor中的类型嵌套层数,将ReverseTuple和FunctionChainArray的组合逻辑扁平化:
type FunctionChainArray<T extends [Fn, ...Fn[]]> = { [K in keyof T]: K extends 0 ? (input: Parameters<T[K]>[0]) => ReturnType<T[K]> : (input: ReturnType<T[K-1]>) => ReturnType<T[K]> } extends infer A ? A extends [Fn, ...Fn[]] ? A : never : never; function compositor<T extends [Fn, ...Fn[]]>( ...functions: ReverseTuple<FunctionChainArray<ReverseTuple<T>>> ) { return functions as unknown as ReverseTuple<FunctionChainArray<ReverseTuple<T>>>; }
3. 移除冗余类型工具
去掉Lookup这类多余的工具类型,直接使用数组索引访问简化类型计算。
修改后的完整代码
type Fn = (input: any) => any; type ReverseTuple< T extends [any, ...any[]], NewArray extends any[] = [] > = T extends [infer F, ...infer R] ? R extends [] ? [F, ...NewArray] : ReverseTuple<R, [F, ...NewArray]> : NewArray; type LinkedFn<F1 extends Fn, F2 extends Fn> = (input: ReturnType<F1>) => ReturnType<F2>; type FunctionChainArray<T extends [Fn, ...Fn[]]> = { [K in keyof T]: K extends 0 ? (input: Parameters<T[K]>[0]) => ReturnType<T[K]> : LinkedFn<T[K-1], T[K]> } extends infer A ? A extends [Fn, ...Fn[]] ? A : never : never; function compositor<T extends [Fn, ...Fn[]]>( ...functions: ReverseTuple<FunctionChainArray<ReverseTuple<T>>> ) { return functions as unknown as ReverseTuple<FunctionChainArray<ReverseTuple<T>>>; } const y2 = compositor( (a: 'Rb') => 'Rb' as 'Rb', (a: 'Ra') => 'Rb' as 'Rb', (a: 'start') => 'Ra' as 'Ra', );
内容的提问来源于stack exchange,提问作者TrevTheDev
相关产品推荐
相关产品推荐

