首页 > 解决方案 > 打字稿中的函数重载和类型推断

问题描述

我有这种类型:

type Bar = number;
type Foo = {
   doIt: ((value: string) => Bar) | ((value: string, provideBar: (bar: Bar) => void) => void);
}

这个想法是Bar可以根据提供的函数签名以两种方式之一返回。使用实现的代码Foo如下所示:

function getBar(foo: Foo) {
  let bar: Bar = 0;

  if (foo.doIt.length === 1) { // We've been provided with the first version of `Foo`
    bar = foo.doIt('hello');  
    // The above line is erroring with: 
    // - bar: Type 'number | void' is not assignable to type 'number'
    // - invocation of "doIt": Expected 2 arguments, but got 1.

  } else if (foo.doIt.length === 2) { // We've been provided with the second version of `Foo`
    foo.doIt('hello', (_bar) => bar = _bar);
  }
}

提供 a 实现的代码Foo如下所示:

function provideBar() {
  const foo1: Foo = {
    doIt: (value) => 1. // Error:  Parameter 'value' implicitly has an 'any' type.
  }

  const foo2: Foo = {
    doIt: (value, provideBar) => provideBar(2) // Appears to be working
  }
}

我希望打字稿有办法表达我想要实现的目标。我不确定为什么会出现这些错误,因为按照我的看法,TS 有足够的信息来阻止能够提供类型推断(我假设 TS 可以function.length用来区分实现 a 的两种方法Foo

标签: typescriptoverloading

解决方案


对于getBar()'s implementation 内部的问题,您遇到了microsoft/TypeScript#18422,在 TypeScript 中列为设计限制。

编译器仅将length函数的属性视为 type number,而不是任何特定的数字文字类型,例如1or 2。所以检查foo.doIt.length === 1控制流分析没有影响,因此编译器不知道它正在调用两种函数类型中的哪一种。

检查的一个主要问题length是它可能不是您认为的那样。函数可以用剩余参数实现,而且length很可能是0

或者因为 TypeScript 允许您将接受较少参数的函数分配给接受更多参数的函数(请参阅此常见问题解答条目),匹配的函数(value: string, provideBar: (bar: Bar) => void) => void可能具有lengthof10因为函数实现可以自由地忽略它们的任何输入。

因为这样的怪异length,TypeScript 基本上什么都不做,建议大家不要尝试这样检查length

尽管如此,如果您确信检查会按照您的想法进行(也就是说,没有人会设置"doIt"为上述“陷阱”版本之一),您可以通过实现用户定义的类型保护函数来获得类似的行为:

function takesOneArg<F extends Function>(x: F): x is Extract<F, (y: any) => any> {
    return x.length === 1
}

takesOneArg()函数检查length其类型的类函数参数,如果相等则F返回,否则返回。返回类型谓词意味着如果是函数类型的联合,则结果意味着可以将类型缩小到只接受一个参数的成员;结果手段可以缩小到其他手段。true1falsex is Extract<F, (y: any) => any>FtruexFfalsex

现在您的getBar()实现按预期工作:

function getBar(foo: Foo) {
    let bar: Bar = 0;
    if (takesOneArg(foo.doIt)) {
        bar = foo.doIt('hello');
    } else {
        foo.doIt('hello', (_bar) => bar = _bar);
    }
}

至于创建一个Foo在回调参数中看到隐式any错误的问题,这似乎是microsoft/TypeScript#37580。您希望value上下文中键入string,但编译器没有这样做。那个 GitHub 问题中没有太多信息,所以我不能说是什么导致了函数联合和上下文类型推断之间的不良交互。

假设这不会很快得到解决,解决方法与我一直推荐的隐式any问题相同:显式注释编译器无法推断的内容:

const foo1: Foo = {
    doIt: (value: string) => 1
}

现在编译没有错误。


Playground 代码链接


推荐阅读