【问题标题】:Infer return type of function based on type guard根据类型保护推断函数的返回类型
【发布时间】:2020-09-28 08:48:19
【问题描述】:

我有以下定义:

function foo(arg: number[] | number) {
    if (Array.isArray(arg)) {
        return [1]
    }

    return 0
}

我希望 typescript 能够自动找出返回类型是什么。由于类型保护isArray(),它知道 arg 是否是一个数组,并且可以将返回类型显示为number[]。但是,使用foo(...) 会显示其返回值为number[] | 0,即使在传递数组时也是如此。

  foo([]).push() // error because push doesnt exist on type 0

这是设计限制、错误、尚未实现还是其他问题?

【问题讨论】:

    标签: typescript


    【解决方案1】:

    我无法明确指出这是一个设计限制,但我看到像 jcalzTitian Cernicova-Dragomir 这样的专家引用了各种类型推断受到限制的地方,有时不是因为它不能做我们想要的,但是因为这样做成本太高(就运行时成本或编译器中的代码复杂性而言)。我怀疑这属于该类别。

    您可能知道这一点,但对于您的具体示例,您可以使用重载来获得您想要的结果:

    function foo(arg: number[]): number[];
    function foo(arg: number): number;
    function foo(arg: number[] | number) {
        if (Array.isArray(arg)) {
            return arg.map(v => v * 2);
        }
        
        return arg * 2;
    }
    foo([]).push(10);
    

    Playground link

    【讨论】:

    【解决方案2】:

    我想你要找的是overloads

    如下使用重载可以解决您的问题:

    
    function foo(arg1: number[]): number[];
    function foo(arg1: number): 0;
    function foo(arg1: any){
        if(Array.isArray(arg1)){
            return [0]
        }
    
        return 0
    }
    

    【讨论】:

      【解决方案3】:

      另一种选择是Generics

      function foo<T extends number[] | number>(arg:T):T {
          if (Array.isArray(arg)) { return [1] as T }
          return 0 as T
      }
      
      foo([] as number[]).push(10) // works
      foo(42).push(10) // error as expected
      

      foo 的消费者得到完美的类型。在里面我们使用类型断言,因为returned 的值不是soundT 的形状是由调用者指定的,所以我们无法知道它到底是什么。

      想象一下,42 恋物癖俱乐部拥有 foo - 所有其他数字都是 mehh... :)

      然后像
      function foo<T extends number[] | number>(arg:T):T {
          if (Array.isArray(arg)) { return [1]}
          return 1
      }
      foo(42); // T instantiated as 42
      

      显然不会被容忍。

      虽然这个例子是人为的——虽然俱乐部听起来很整洁——但你可能明白了,为什么第一个例子必须是type asserted

      一个更真实的声音样本:
      function ensureDefined<T extends number[] | number>(initializer: () => T, arg?: T): T {
          if (arg === undefined || Array.isArray(arg) && arg.length === 0) {
              return initializer()
          } else return arg
      }
      
      ensureDefined(() => [42], [] as number[]).push(10) // works
      ensureDefined(()=> 42, undefined) // error as expected
      

      Here is a Playground.

      【讨论】:

        猜你喜欢
        • 2020-02-28
        • 1970-01-01
        • 1970-01-01
        • 2020-12-26
        • 1970-01-01
        • 2016-05-02
        • 1970-01-01
        • 2020-06-06
        • 1970-01-01
        相关资源
        最近更新 更多