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

TypeScript类型实例化过深问题:解决方案与替代方案咨询

类型递归过深导致TypeScript编译错误的解决方案咨询

问题背景

以下代码实现了输入输出链式结构,链中每个节点仅关注前驱节点的Output并将其作为自身Input。TypeScript会构建极长的类型,导致编译器卡顿直至不可用,最终触发Type instantiation is excessively deep and possibly infinite.(2589)错误。此简化代码在第6个节点触发该限制,但实际代码中仅2个节点就会触发。

咨询问题

  1. 该问题是否有解决方案?参考相关Issue,我猜测答案是否定的。
  2. 能否提供可实现类似效果的替代方案?
  3. 是否可以扁平化类型,避免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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 14:36:16