简体   繁体   English

Function typescript 中的重载和类型推断

[英]Function overloading and type inference in typescript

I have this type:我有这种类型:

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

The idea is that Bar can be returned in one of two ways depending on which function signature is provided.这个想法是Bar可以通过两种方式之一返回,具体取决于提供的 function 签名。 The code which consumes an implementation of Foo looks like:使用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);
  }
}

Code which would provide an implementation of a Foo looks like:提供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
  }
}

I'm hopeful typescript has a way to express what I'm trying to achieve.我希望 typescript 有办法表达我想要实现的目标。 I'm not sure why I'm getting these errors as, the way I see it, TS has enough information to bar able to provide type inference (I'm assuming TS can use function.length to deferentiate between the two ways to implement a Foo )我不确定为什么会出现这些错误,因为按照我的看法,TS 有足够的信息来阻止能够提供类型推断(我假设 TS 可以使用function.length来区分两种实现方式一个Foo )

For the issue inside getBar() 's implementation, you're running into microsoft/TypeScript#18422 , listed as a design limitation in TypeScript.对于getBar()实现中的问题,您遇到了microsoft/TypeScript#18422 ,在 TypeScript 中列为设计限制。

The compiler only sees the length property of a function as being of type number , and not any specific numeric literal type like 1 or 2 .编译器仅将 function 的length属性视为number类型,而不是任何特定的数字文字类型,如12 So checking foo.doIt.length === 1 has no implication for control flow analysis , and thus the compiler does not know which of the two function types it is calling.因此检查foo.doIt.length === 1控制流分析没有任何影响,因此编译器不知道它正在调用的两种 function 类型中的哪一种。

One major problem with checking length is that it might well not be what you think it is.检查length的一个主要问题是它可能不是你想象的那样。 Functions can be implemented with rest parameters , and length might well be 0 .功能可以用rest参数实现, length可能为0

Or because TypeScript allows you to assign functions that accept fewer parameters to those that accept more (see this FAQ entry ), it's possible that a function that matches (value: string, provideBar: (bar: Bar) => void) => void might have a length of 1 or 0 because function implementations are free to ignore any of their inputs.或者因为 TypeScript 允许您将接受较少参数的函数分配给接受更多参数的函数(请参阅此常见问题解答条目),因此 function 可能匹配(value: string, provideBar: (bar: Bar) => void) => void length可能为10 ,因为 function 实现可以随意忽略它们的任何输入。

Because of such weirdness around length , TypeScript basically doesn't do anything and recommends that you don't try to check length this way.由于length周围的这种怪异, TypeScript 基本上什么都不做,建议您不要尝试以这种方式检查length

Still, if you are confident that the check does what you think it does (that is, nobody will set "doIt" to one of the "gotcha" versions above), you can get similar behavior by implementing a user-defined type guard function :尽管如此,如果您确信检查会按照您的想法进行(也就是说,没有人会将"doIt"设置为上述“gotcha”版本之一),您可以通过实现用户定义的类型保护 function来获得类似的行为:

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

The takesOneArg() function checks the length of its function-like argument of type F and returns true if it equals 1 and false otherwise. takesOneArg() function 检查其类型F的类函数参数的length ,如果等于1 ,则返回true ,否则返回false The return type predicate x is Extract<F, (y: any) => any> means that if F is a union of function types, a true result means that the type of x can be narrowed to just the members of F that take one argument;返回类型谓词x is Extract<F, (y: any) => any>意味着如果F是 function 类型的并集,则true的结果意味着x的类型可以缩小到F的成员一个论点; and a false result means x can be narrowed to the other ones. false的结果意味着x可以缩小到其他的。

Now your getBar() implementation works as expected:现在您的getBar()实现按预期工作:

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

As for the issue when you create a Foo where you see an implicit any error in the callback argument, this seems to be microsoft/TypeScript#37580 .至于创建Foo时的问题,您会在回调参数中看到隐含的any错误,这似乎是microsoft/TypeScript#37580 You would like value to be contextually typed as string , but the compiler is not doing so.您希望value上下文中键入string ,但编译器没有这样做。 There's not a lot of info in that GitHub issue, so I can't say what's causing the poor interaction between the function union and contextual typing inference.在 GitHub 问题中没有太多信息,所以我不能说是什么导致了 function 联合和上下文类型推断之间的不良交互。

Assuming this doesn't get fixed anytime soon, the workaround is the same one I'd always recommend with implicit any problems: explicitly annotate the thing the compiler can't infer:假设这不会很快得到解决,解决方法与我总是推荐的解决方法相同,但隐含any问题:显式注释编译器无法推断的内容:

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

That now compiles without error.现在编译没有错误。


Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM