扩展类型的泛型和 Typescript 中的普通类型有啥区别?

Posted

技术标签:

【中文标题】扩展类型的泛型和 Typescript 中的普通类型有啥区别?【英文标题】:What is the difference between a generic that extends a type and a normal type in Typescript?扩展类型的泛型和 Typescript 中的普通类型有什么区别? 【发布时间】:2020-05-23 17:32:00 【问题描述】:

Typescript 专家您好,

有人可以解释为什么下面的代码在第 16 行给我一个错误,但在第 13 行没有。这是有意的还是缺少的功能?

代码

interface Config 
  // There need to be different types in here for the error to occur
  A: number
  B: string


type union = "A" | "B" 

var Global = A: 1, B: "Hello" as Config

function foo<K extends union>(key: K, x: union) 
  if (x === "A")
    Global[x].toFixed()
  
  if (key === "A")
    Global[key].toFixed()
  

Playground Link

【问题讨论】:

【参考方案1】:

这可能是缺少的功能。

当您检查(x === "A") 时,它在值x 的类型上充当type guard,导致它通过control flow analysis 从联合类型"A"|"B" 缩小到仅"A"

不幸的是,TypeScript 中的泛型类型参数无法通过控制流分析来缩小范围;请参阅microsoft/TypeScript#24085 了解更多信息。因此,例如,检查(key === "A")不会K 的类型缩小到"A"。当您有多个相同泛型类型的值时,这种限制是有意义的:

function foo<K extends Union>(key: K, x: Union, key2: K)     
  if (key2 === "A") 
    Global[key].toFixed(); // error!
  

显然检查key2 的值应该对key 的类型没有影响,因此编译器保守地认为不应该缩小K。这是 microsoft/TypeScript#13995 中的基本问题,并且已经提出了多个相关问题,并提出了有关如何在应该安全地进行此类缩小的情况下处理它的建议。不过,到目前为止,还没有任何东西成为这种语言。


不过,这并不是完整的故事;有人可以反驳:好吧,也许你不能将 type 参数 KK extends Union 缩小到 K extends "A",但是肯定 您可以将 key 的类型从K 缩小到"A"K &amp; "A"(intersection type),这将使Global[key].toFixed() 成功:

if (key === "A") 
  Global[key as (K & "A")].toFixed(); // okay

我现在对此没有很好的答案。我看到的关于这个的大多数问题最终都被提到了 microsoft/TypeScript#13995。 ?‍♂️

我能得出的最接近完整答案的是,似乎使用像 a === btypeof a === "string"a instanceof Ba in b 这样的内置类型保护最终只会过滤联合或可能缩小 stringnumber 为字符串或数字文字,但 从不 产生交集类型。我已经 asked before, see microsoft/TypeScript#21732, 为 a in b 类型守卫产生了一些交叉点,但它还没有实现。所以这可能是两个缺少的功能:

没有缩小泛型类型参数,并且 没有内置类型防护装置缩小到交叉路口。

因此,变通方法:显然,对于这个示例来说,最简单的方法就是将泛型类型变量的值重新分配给联合类型变量:

const k: Union = key;
if (k === "A") 
  Global[k].toFixed();

或者,您可以使用type assertion,如上面的as (K &amp; "A") 或只是as "A"

if (key === "A") 
  Global[key as (K & "A")].toFixed(); // okay
  Global[key as "A"].toFixed(); // okay

或者,如果这种情况经常发生,您可以编写自己的user-defined type guard 函数,因为用户定义的类型保护确实会在后续控制流的true 分支中产生交集:

const isEq =
  <T extends string | number | boolean>(v: any, c: T): v is T => v === c;

if (isEq(key, "A")) 
  Global[key].toFixed(); // okay, key is now of type K & "A";

Playground link to code

【讨论】:

【参考方案2】:

我不确定为什么会发生该错误,但我相信有人有很好的解释。这是通过将intersection types 与泛型一起使用来实现相同目的的另一种方法:

interface Config 
  // There need to be different types in here for the error to occur
  A: number
  B: string


type Union = keyof Config;

const Global: Config =  A: 1, B: "Hello" ;

function foo<K>(key: K & Union, x: Union)  // Use intersection type instead of inheritance
  if (x === "A")
    Global[x].toFixed();
  
  if (key === 'A')
    Global[key].toFixed()
  

Playground link

另请参阅此问题:Difference between extending and intersecting interfaces in TypeScript?

【讨论】:

以上是关于扩展类型的泛型和 Typescript 中的普通类型有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

总结java的泛型和内部类

泛型和枚举

java笔记Java中的泛型和类通配符

Kotlin小知识之泛型和委托

java里的泛型和通配符

不明白java中的泛型和抽象类有啥区别,感觉他们作用一样啊,为啥要用2种方法呢