繁体   English   中英

function 工厂 typescript 的不同返回类型

[英]Different return type for function factory typescript

我正在等待从具有所需参数数量的交换机获得 function。 但它不起作用。 我正在等待 fun1 0 arguments 因为 PrintSomething() 没有 arguments 但有错误 //预期 1 arguments,但得到了 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');
}

TypeScript 编译器不会尝试合成 function 签名,该签名表示 function 实现中的控制流分析结果。

它所做的只是产生类似于实现中返回的所有值的类型的联合 对于您的代码,如下所示:

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

如果您想知道() => void去了哪里,那是因为() => void可以分配给该工会的两个成员。 请参阅有关比较函数的文档,以了解有关原因的详细信息。 所以它被吸收在签名中(就像string | "a"变成了string )。

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

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


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

您对这种类型的选择应该是什么样的:

重载

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

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

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

通用条件

您可以使getFun成为返回条件类型通用function ,如下所示:

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成为一个通用的 function 索引到映射接口,如下所示:

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的这三种类型中,编译器无法验证实现与它们中的至少两种匹配。 这是一个难题。

对于重载,在microsoft/TypeScript#13235microsoft/TypeScript#17747中有建议让编译器了解 function 准确地实现了一组重载调用签名,或合成这样一组调用签名。 这些被关闭是因为对编译器来说太复杂了。

对于泛型条件类型,在microsoft/TypeScript#33912中有一个公开建议,让编译器了解 function 准确地实现了泛型条件类型。 但它列出了这是一个很难解决的问题,原因与重载类似; 这对编译器来说是很多工作。

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


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

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具有相同的类型。 因此,这将是我为您的示例推荐的方法,因为您可以获得强类型和类型安全性。

Playground 代码链接

暂无
暂无

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

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