函数工厂打字稿的不同返回类型

时间:2021-06-06 17:23:51

标签: typescript

我正在等待具有所需数量参数的 switch 函数。但它不起作用。我正在等待 fun1 0 个参数,因为 PrintSomething() 没有参数但有错误 //预期 1 个参数,但得到 0。 有什么想法吗?

type opt = 'number1' | 'string2' | 'something3'

export class Factory {
  static getFun = (option: opt) => {
    switch (option) {
      case 'number1':
        return NumberFun;
      case 'string2':
        return StringFun;
      case 'something3':
        return PrintSomething;
    }
  };
}

//getResult(option: string): ((val: number) => void) | ((str: string) => void)
//but what about PrintSomething() and type ()=>void????
let fun1 = Factory.getFun('something3');

fun1()

function NumberFun(val: number) {
  console.log(val);
}

function StringFun(str: string) {
  console.log(str);
}

function PrintSomething() {
  console.log('something');
}

1 个答案:

答案 0 :(得分:2)

TypeScript 编译器不会尝试在函数实现中合成表示 control flow analysis 结果的函数签名。

它所做的只是产生类似于实现中返回的所有值的类型的 union 的东西。对于您的代码,这看起来像:

(option: Opt) => ((val: number) => void) | ((str: string) => void)

如果您想知道 () => void 去哪里了,那是因为 () => void 可以分配给该联合的两个成员。有关原因的详细信息,请参阅 the documentation on comparing functions。所以它被吸收在签名中(比如 string | "a" 变成了 string)。

编译器何时以及如何从联合执行子类型缩减的规则本质上是启发式的,所以我不知道我是否能准确解释他们为什么决定不推断 ((val: number) => void) | ((str: string) => void) | (() => void),但重点是编译器的推断的函数类型与实现一致。

它只是不适合您的预期用途。


如果您希望 Factory.getFun() 具有强类型的输入输出关系,其中返回类型取决于输入值,您将不得不自己写出这种关系,甚至可能写出 {{3} } 该值属于该类型,因为编译器无法轻松验证何时可以将实现分配给此类类型。

您对此类型的选择:

过载

您可以使 getFun 成为具有三个调用签名的 assert 类型,如下所示:

interface GetFunOverloaded {
  (option: "number1"): typeof NumberFun,
  (option: "string2"): typeof StringFun,
  (option: "something3"): typeof PrintSomething
};

  static getFun = ((option: Opt) => {
    // impl here
  }) as GetFunOverloaded;

通用条件

您可以使 getFun 成为返回 overloadedgeneric 函数,如下所示:

interface GetFunConditional {
  <O extends Opt>(option: O):
    O extends 'number1' ? typeof NumberFun :
    O extends 'string2' ? typeof StringFun :
    O extends 'something3' ? typeof PrintSomething :
    never;
}

  static getFun = ((option: Opt) => {
    // impl here
  }) as GetFunConditional;

通用索引访问

最后,您可以使 getFun 成为一个通用函数,它 conditional type 成为映射接口,如下所示:

interface OptMap {
  number1: typeof NumberFun,
  string2: typeof StringFun,
  something3: typeof PrintSomething
}

interface GetFunIndexed {
  <O extends keyof OptMap>(option: O): OptMap[O];
}

  static getFun = ((option: Opt) => {
    // impl here
  }) as GetFunIndexed;

所有这三个都将在调用方方面起作用,至少对于您在此处显示的用例而言:

let fun1 = Factory.getFun('something3');
// let fun1: () => void
fun1() // okay

但是在 getFun 的这三种类型中,编译器无法验证实现至少与其中两种匹配。这是一个难题。

对于重载,在 indexesmicrosoft/TypeScript#13235 中有建议让编译器理解一个函数准确地实现了一组重载调用签名,或者合成这样一组调用签名。由于对编译器来说太复杂,这些被关闭了。

对于泛型条件类型,microsoft/TypeScript#17747 处有一个公开建议,让编译器了解函数准确地实现了泛型条件类型。但它被列为一个难以解决的问题,原因与重载类似;编译器需要做很多工作。

因此,对于这些,您需要类型断言 as GetFun,如上所示。


编译器能够验证实现是否符合通用索引访问签名,但不能验证使用 switch 的特定实现。它仅在您索引对象时才有效,如下所示:

const optMap: OptMap = {
  number1: NumberFun,
  string2: StringFun,
  something3: PrintSomething
}

export class Factory {
  static getFun = <O extends keyof OptMap>(option: O) => optMap[option];
}
// Factory.getFun: <O extends keyof OptMap>(option: O) => OptMap[O]
let fun1 = Factory.getFun('something3');
// let fun1: () => void
fun1() // okay

注意编译器如何推断 getFun()GetFun 具有相同的类型。因此,这将是我为您的示例推荐的方法,因为您可以获得强类型和类型安全。

microsoft/TypeScript#33912

相关问题