TypeScript类型实例化过深问题:解决方案与替代方案咨询
类型递归过深导致TypeScript编译错误的解决方案咨询
问题背景
以下代码实现了输入输出链式结构,链中每个节点仅关注前驱节点的Output并将其作为自身Input。TypeScript会构建极长的类型,导致编译器卡顿直至不可用,最终触发Type instantiation is excessively deep and possibly infinite.(2589)错误。此简化代码在第6个节点触发该限制,但实际代码中仅2个节点就会触发。
咨询问题
- 该问题是否有解决方案?参考相关Issue,我猜测答案是否定的。
- 能否提供可实现类似效果的替代方案?
- 是否可以扁平化类型,避免TypeScript生成过长、复杂的类型?
原实现代码
type LMerge<T1, T2> = { [k in keyof T1]: k extends keyof T2 ? T2[k] : T1[k] } type ChainNodePossibleGenerics = { Output?: unknown } type ChainNodeGenerics = { Output: unknown Input: unknown Depth: unknown } type RecursionNext = [1, 2, 3, 4, 5, 6, 7, 8, 9, never] type CurrentDepth<Node extends ChainNodeGenerics> = Node['Depth'] extends number ? RecursionNext[Node['Depth']] : [Node['Depth']] extends [never] ? never : 0 type NextNode< Node extends ChainNodeGenerics, Child extends ChainNodePossibleGenerics, DefaultsApplied = LMerge<{ Input: Node['Output']; Output: unknown }, Child>, NormalisedChild extends ChainNodeGenerics = LMerge<ChainNodeGenerics, DefaultsApplied>, MergedType extends ChainNodeGenerics = LMerge< NormalisedChild, { Depth: CurrentDepth<Node> } >, > = MergedType type ChainNode<Node extends ChainNodeGenerics> = { <T extends ChainNodePossibleGenerics = {}, Child extends ChainNodeGenerics = NextNode<Node, T>>( asyncFn: any, ): ChainNode<Child> depth: Node['Depth'] input: Node['Input'] output: Node['Output'] } /* ***** EXAMPLE USAGE *************** */ let cN0: ChainNode<LMerge<ChainNodeGenerics, { Output: 'stringI' }>> // @ts-ignore const cN1 = cN0<{ Output: 'A' }>('a') const d = cN1.depth // 0 const cN1Input = cN1.input // 'stringI' const cN1Output = cN1.output // 'A' const cN2 = cN1<{ Output: 'B' }>('b') const d2 = cN2.depth // 1 const cN2Input = cN2.input // 'A'' const cN2Output = cN2.output // 'B' const cN3 = cN2<{ Output: 'C' }>('c') const d3 = cN3.depth // 2 const cN3Input = cN3.input // 'B' const cN3Output = cN3.output // 'C' const cN4 = cN3<{ Output: 'D' }>('d') const d4 = cN4.depth // 3 const cN4Input = cN4.input // 'C' const cN4Output = cN4.output // 'D' const cN5 = cN4<{ Output: 'E' }>('e') const d5 = cN5.depth // 4 const cN5Input = cN5.input // 'D' const cN5Output = cN5.output // 'E' const cN6 = cN5<{ Output: 'F' }>('f') // Type instantiation is excessively deep and possibly infinite.(2589) const d6 = cN6.depth // any!!!! const cN6Input = cN6.input // any const cN6Output = cN6.output // any const cN7 = cN6<{ Output: 'G' }>('g') const d7 = cN7.depth // any!!!! const cN7Input = cN7.input // any const cN7Output = cN7.output // any
问题解答
1. 该问题是否有解决方案?
目前没有完美的解决方案。TypeScript的类型系统对泛型递归深度有硬限制,这类通过嵌套泛型构建的链式类型会快速触发2589错误。相关核心Issue显示,TypeScript团队暂时不会彻底放宽这类递归限制,因为会导致编译器性能大幅下降。
2. 可实现类似效果的替代方案
方案一:显式传递类型参数
放弃自动递归推导,每次调用时直接指定或推导当前节点的输入输出类型:
type ChainNode<Input, Output, Depth extends number> = { <NewOutput>(asyncFn: (input: Input) => Promise<NewOutput>): ChainNode<Output, NewOutput, Depth extends number ? Depth + 1 : 0> depth: Depth input: Input output: Output } // 初始化节点 function createChainNode<Output>(): ChainNode<never, Output, 0> { return { depth: 0 as any, input: undefined as any, output: undefined as any, asyncFn: () => Promise.resolve(undefined as any) } as any } // 使用示例 const cN0 = createChainNode<'stringI'>() const cN1 = cN0<'A'>(() => Promise.resolve('A')) const cN2 = cN1<'B'>(() => Promise.resolve('B')) // 无递归深度错误,类型推导正常
方案二:用类型记录替代链式泛型
将整个链的步骤记录为元组类型,避免嵌套泛型:
type ChainStep<Input, Output> = { input: Input; output: Output } type Chain<Steps extends ChainStep<any, any>[]> = { add<NewOutput>(asyncFn: (input: Steps[Steps['length']-1]['output']) => Promise<NewOutput>): Chain<[...Steps, ChainStep<Steps[Steps['length']-1]['output'], NewOutput>]> steps: Steps currentInput: Steps[Steps['length']-1]['input'] currentOutput: Steps[Steps['length']-1]['output'] depth: Steps['length'] } function initChain<InitialOutput>(): Chain<[ChainStep<never, InitialOutput>]> { return { steps: [{ input: undefined as never, output: undefined as InitialOutput }], currentInput: undefined as never, currentOutput: undefined as InitialOutput, depth: 1, add: () => initChain() as any } as any } // 使用示例 const chain = initChain<'stringI'>() .add(() => Promise.resolve('A')) .add(() => Promise.resolve('B')) .add(() => Promise.resolve('C')) // 所有类型正确推导,无递归问题
3. 是否可以扁平化类型?
完全可以。核心是避免让TypeScript维护嵌套的泛型链,将每个节点的输入、输出、深度作为独立的扁平泛型参数:
// 扁平化后的ChainNode类型 type ChainNode<Input, Output, Depth extends number> = { <NewOutput>(fn: (input: Input) => any): ChainNode<Output, NewOutput, Depth extends number ? Depth + 1 : 0> depth: Depth input: Input output: Output } // 初始化函数 const createChain = <InitialOutput>(): ChainNode<never, InitialOutput, 0> => ({ depth: 0 as any, input: undefined as any, output: undefined as any, (fn) => createChain() as any } as any) // 使用示例 const c0 = createChain<'stringI'>() const c1 = c0<'A'>(() => 'A') // ChainNode<'stringI', 'A', 0> const c2 = c1<'B'>(() => 'B') // ChainNode<'A', 'B', 1>
这种方式下,每个节点的类型都是独立的扁平结构,TypeScript无需解析嵌套泛型,自然不会触发深度限制错误。
内容的提问来源于stack exchange,提问作者TrevTheDev
相关产品推荐
相关产品推荐

