【问题标题】:Error when using recursive type in a function在函数中使用递归类型时出错
【发布时间】:2021-12-30 13:33:58
【问题描述】:

我有一个递归类型,它从嵌套对象中提取索引并将它们放入一个扁平的强类型元组中,如下所示:

type NestedRecord = Record<string, any>

type RecursiveGetIndex<
  TRecord extends NestedRecord,
  TPreviousIndices extends any[] = []
> = {
  [K in keyof TRecord]: TRecord[K] extends NestedRecord
    ? RecursiveGetIndex<
        TRecord[K],
        AppendToTuple<TPreviousIndices,K>
      >
    : AppendToTuple<TPreviousIndices, K>
}[keyof TRecord]

type AppendToTuple<T extends any[], U> = [...T, U] 

当使用嵌套对象类型的具体实例调用时,它可以正常工作,例如:

type AnIndex = RecursiveGetIndex<{ foo: { bar: { baz: number}}}> 

换句话说,正如预期的那样,AnIndex 被键入为["foo", "bar", "baz"]

但是,当我尝试在函数中使用 Recursive 类型时,出现递归错误:

function doSomething<T extends NestedRecord>(arg:T){
    type Nested = RecursiveGetIndex<T>
    const doMore = (n: Nested) => {
        console.log(n) //Type instantiation is excessively deep and possibly nested
    }
}

哪一种对我来说有意义,因为 TS 并不知道T 的递归深度可能有多大。

正确吗?为什么 TS 不等到它看到 doSomething 被实例化后才担心递归?

而且,如果我事先知道传递给函数的arg 永远不会超过 TS 递归限制(我相信是 50),是否有某种方法可以避免该错误。即,我可以以某种方式告诉 typescript 吗?

我已经看到了基本上通过使用递减数组类型来限制递归的解决方案,例如this SO answer。这是我能做到的最好的吗?

Playground with the code.

更新

根据船长-yossarian 的建议,以下工作:

type RecursiveGetIndex<
  TRecord extends NestedRecord,
  TPreviousIndices extends any[] = [],
  TDepth extends number = 5
> = 10 extends TPreviousIndices["length"] ? TPreviousIndices : {
  [K in keyof TRecord]: TRecord[K] extends NestedRecord
    ? RecursiveGetIndex<
        TRecord[K],
        AppendToTuple<TPreviousIndices,K>
      >
    : AppendToTuple<TPreviousIndices, K>
}[keyof TRecord]

但是,如果此数字大于 10,则会再次出现错误。我原以为应该是 50。我的类型是否比我想象的更递归?

Updated playground.

【问题讨论】:

  • 这里catchts.com/recursive-ds你可以找到有限制的递归类型。至于第二个例子,TS 等不及你调用这个函数了,因为这是两个不同的阶段
  • blah,像这样的递归类型以一种奇怪/有趣的方式进行评估,我不知道有什么好的方法来处理这里的问题。反正我没发现什么好东西。
  • @captain-yossarian,这很聪明。在我的代码中使用它看起来像以= 10 extends TPreviousIndices["length"] ? TPreviousIndices : //etc 开头的类型有效,但如果我将其设为 11,则会出现错误。出于 TS 限制的目的,每个级别的递归都超过 1 是否有某种原因?
  • @sam256 你能提供一个你如何称呼它的例子吗?
  • 我将编辑我的问题,以便我可以放入操场链接

标签: typescript typescript-generics


【解决方案1】:

RecursiveGetIndex&lt;NestedRecord&gt; is the real problem here 这是因为any 是如何用于条件类型的。

由于您的泛型被限制为NestedRecord,因此打字稿必须尝试将RecursiveGetIndex 应用于函数内部的约束以进行类型检查,但这意味着TRecord[K]any,因此条件@ 987654329@ 最终会评估两个分支,因为一个分支最终会以同样的方式再次调用 RecursiveGetIndex

一旦TRecord=any 使用您的原始实现,它就会永远继续寻找嵌套键。因此,您可以添加对any 的检查并在这种情况下仅解析为...any[],这样它就不会陷入无限递归。 playground

type NestedRecord = Record<string, any>

type RecursiveGetIndex<
  TRecord extends NestedRecord,
  TPreviousIndices extends any[] = []
> = {
   // first check for any with 0 extends <clearly not 0> and opt out of recursing with just any nested keys
   // optionally you could use ...unknown[] if you wanted to be safer.
  [K in keyof TRecord]: 0 extends TRecord[K]&1 ? [...TPreviousIndices, K, ...any[]] 
  : TRecord[K] extends NestedRecord
    ? RecursiveGetIndex<
        TRecord[K],
        AppendToTuple<TPreviousIndices,K>
      >
    : AppendToTuple<TPreviousIndices, K>
}[keyof TRecord]


type AppendToTuple<T extends any[], U> = [...T, U] 

type AnIndex = RecursiveGetIndex<{ foo: { bar: number, baz: any}}> 
//   ^? ["foo", "bar"] | ["foo", "baz", ...any[]]

function doSomething<T extends NestedRecord>(arg:T){
    type Nested = RecursiveGetIndex<T>
    const doMore = (n: Nested) => {
        console.log(n) //here n will work a lot like unknown
    }
}

【讨论】:

  • 这对我来说似乎是正确的,因为当我在条件类型中有 any 约束时,我确实倾向于遇到这些问题。但是你能再解释一下第二段吗?具体来说,当它达到两层深度时,为什么它会通过any?我错过了一个步骤。
  • 哦,两层深,你的意思是在函数中,而不是递归,对吗?
  • 是的,术语很难,我编辑试图更清楚,但我知道我有多成功。关键是any 使最合理的递归类型以令人讨厌的方式中断,因为它评估条件的两个分支,因此您经常必须显式检查它以停止无限递归。
  • 您的编辑确实让我更清楚。谢谢。这是我正在从事的项目中两个棘手的 TS 递归问题之一,这让我觉得我需要一个非递归解决方案......
猜你喜欢
  • 2020-11-05
  • 2021-03-16
  • 2020-03-22
  • 2020-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-20
  • 2022-01-06
相关资源
最近更新 更多