在Typescript中扩展类型的泛型和普通类型之间有什么区别?

时间:2020-02-07 14:54:20

标签: typescript typescript-generics

您好,打字稿专家

有人可以解释为什么下面的代码在第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

2 个答案:

答案 0 :(得分:1)

可能是缺少的功能。

当您检查(x === "A")时,它会作为值x的类型上的type guard,导致其从联合类型"A"|"B"缩小为{{1 }}通过control flow analysis

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

"A"

显然,检查function foo<K extends Union>(key: K, x: Union, key2: K) { if (key2 === "A") { Global[key].toFixed(); // error! } } 的值应该对key2的类型没有影响,因此编译器保守地不认为应该缩小key的范围。这是microsoft / TypeScript#13995中的基本问题,并且在应该安全地进行这种范围缩小的情况下,提出了有关如何处理它的建议的多个相关问题。到目前为止,还没有任何东西成为语言。


但这并不是全部内容;一个人可能会反驳:好吧,也许您不能将 type参数 K的范围从K缩小到K extends Union,但是< em>确定,您可以将 value K extends "A"的类型从key缩小为K"A"({{3 }}),这将使K & "A"成功:

Global[key].toFixed()

我现在对此还没有很好的答案。我最终看到的大多数问题最终都被引用到microsoft / TypeScript#13995。 ?‍♂️

我能得到的最接近的完整答案是,似乎只能使用if (key === "A") { Global[key as (K & "A")].toFixed(); // okay } a === btypeof a === "string"a instanceof B这样的内置类型防护过滤并集或将a in bstring缩小为字符串或数字文字,但是从不会生成交集类型。我已经为number类型防护提供了intersection type来产生一些交集,但尚未实现。因此,这可能是两个缺少的功能:

  • 不缩小通用类型参数,并且
  • 没有可缩小到交叉点的内置类型防护。

因此,解决方法:显然,此示例最简单的方法是将泛型变量的值重新分配为联合类型变量:

a in b

或者,您可以像上面的const k: Union = key; if (k === "A") { Global[k].toFixed(); } 一样使用asked before, see microsoft/TypeScript#21732,或仅使用as (K & "A")

as "A"

或者,如果经常发生这种情况,则可以编写自己的type assertion函数,因为用户定义的类型防护 do 在后续项的if (key === "A") { Global[key as (K & "A")].toFixed(); // okay Global[key as "A"].toFixed(); // okay } 分支中产生交集控制流:

true

希望其中之一对您有所帮助。好的,祝你好运!

user-defined type guard

答案 1 :(得分:0)

我不确定为什么会发生该错误,但是我确定有人有很好的解释。这是通过将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?