具有特定深度的 TypeScript 递归类型

mat*_*zav 8 recursion types typescript

TypeScript 允许您编写递归类型,但无法深入了解代码在较低级别(即深度)中如何变化。例如,下面的代码在所有级别上都具有相同类型的签名,并且我们必须在每个级别上手动检查是否存在属性sub

type Recurse = { foo: string; sub?: Recurse }

function recurse(depth: number): Recurse {
  if (depth === 0) return { foo: 'hey' }
  return {
    foo: 'hey',
    sub: recurse(depth - 1),
  }
}
const qux = recurse(5)
Run Code Online (Sandbox Code Playgroud)

我正在寻找的是一个类型签名,它可以为我们提供函数在特定深度返回的内容的具体证据。

const qux0: { foo: string } = recurse(0)
const qux1: { foo: string, sub: { foo: string } } = recurse(1)
const qux2: { foo: string, sub: {  foo: string, sub: { foo: string }} } = recurse(2)
Run Code Online (Sandbox Code Playgroud)

这样,我们就不必在每个级别检查属性,sub因为类型签名已经包含该信息。

我有一种感觉,这可能可以通过条件类型来实现,但没有具体的证据。

你知道我怎样才能实现这一目标吗?

for*_*d04 8

在 TS 允许对类型进行基本算术运算之前number,您可以使用递减计数器,就像用于Decr具有特定深度的所有递归类型一样:

type Decr = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // add to a reasonable amount

type Recurse<N extends number> = N extends 0 ? 
  { foo: string } : 
  { foo: string; sub: Recurse<Decr[N]> }

function recurse<N extends number>(depth: N): Recurse<N> {
  if (depth === 0) return { foo: 'hey' } as Recurse<N>
  return {
    foo: 'hey',
    sub: recurse(depth - 1),
  } as Recurse<N>
}
Run Code Online (Sandbox Code Playgroud)
type Decr9 = Decr[9] // 8

// compiles now
const qux = recurse(5)
const qux0: { foo: string } = recurse(0)
const qux1: { foo: string, sub: { foo: string } } = recurse(1)
const qux2: { foo: string, sub: { foo: string, sub: { foo: string } } } = recurse(2)
Run Code Online (Sandbox Code Playgroud)

样本


Tit*_*mir 4

我认为您可能可以得到一些与递归条件类型一起使用的东西,但我建议不要使用这样的解决方案。毕竟,如果有人调用, recurse(5000)返回类型将会很大。

您可以轻松地基于映射类型构建解决方案,在其中添加子类型直至特定深度:

type Recurse<T extends number =  number> = {
  foo: string;
  sub: RecurseChildren extends Record<T, infer C> ? C : Recurse
}

interface RecurseChildren {
  0: undefined;
  1: Recurse<0>
  2: Recurse<1>
  3: Recurse<2>
  4: Recurse<3>
  5: Recurse<4>
  6: Recurse<5>
  7: Recurse<6>
  8: Recurse<7>
  9: Recurse<8>
  10: Recurse<9>
}


function recurse<T extends number>(depth: T): Recurse<T> {
  if (depth === 0) return { foo: 'hey', sub: undefined as any }
  return {
    foo: 'hey',
    sub: recurse(depth - 1) as any,
  }
}
const qux = recurse(5)

qux.sub.sub.sub.sub.sub.sub // last one is undefined 
Run Code Online (Sandbox Code Playgroud)

游乐场链接