【问题标题】:TypeScript recursive type with particular depth具有特定深度的 TypeScript 递归类型
【发布时间】:2020-05-26 20:29:42
【问题描述】:

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)

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

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)

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

我觉得这可以通过条件类型实现,但没有具体证据。

你知道我是怎么做到的吗?

【问题讨论】:

    标签: typescript recursion types


    【解决方案1】:

    我认为您可能可以使用递归条件类型,但我建议您不要使用这种解决方案。毕竟,如果有人调用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 
    

    Playground Link

    【讨论】:

      【解决方案2】:

      在 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>
      }
      
      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)
      

      Sample

      【讨论】:

        【解决方案3】:

        其他答案很好,但它们都使用预定义的值,您必须提供范围。但是,您可以使用另一个技巧让 TypeScript 为您进行检查:

        type Recurse<N extends number> = Recurse_<N, []>;
        type Recurse_<
            N extends number,
            Depth extends unknown[] = unknown[]
        > = N extends Depth['length']
            ? { foo: string }
            : { foo: string; sub: Recurse_<N, [N, ...Depth]> };
        
        function recurse<Depth extends number>(depth: Depth): Recurse<Depth> {
            if (depth <= 0) return { foo: 'hey' } as Recurse<Depth>;
            return {
                foo: 'hey',
                sub: recurse(depth - 1),
            } as Recurse<Depth>;
        }
        
        const qux = recurse(40);
        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);
        

        Check the playground

        【讨论】:

          猜你喜欢
          • 2017-07-03
          • 1970-01-01
          • 2019-05-18
          • 2013-02-06
          • 1970-01-01
          • 2018-05-30
          • 1970-01-01
          • 2018-04-14
          • 1970-01-01
          相关资源
          最近更新 更多