【问题标题】:Variable of union type causes error in switch statementunion 类型的变量导致 switch 语句出错
【发布时间】:2023-03-10 15:35:01
【问题描述】:

假设我们有一个联合类型,它代表三个不同的字符串值之一。

type Animal = 'bird' | 'cat' | 'dog';

现在我想创建一只狗并检查它是什么动物来产生正确的噪音。

let oscar: Animal = 'dog';

switch (oscar) {
  case 'bird':
    console.log('tweet');
    break;
  case 'cat':
    console.log('meow');
    break;
  case 'dog':
    console.log('bark');
    break;
}

此代码将导致 TypeScript 错误:Type '"bird"' is not comparable to type '"dog"'.ts(2678)(与 cat 类似)。但是,如果我在变量 oscar 上使用显式类型转换,则它可以正常工作:

switch (oscar as Animal) {
  case 'bird':
    ...
  case 'cat':
    ...
  case 'dog':
    ...
}

如果我对 oscar 使用显式值,您能否解释一下为什么前两个 switch 语句会失败?

如果我将 Oscar 声明为常量,我可以理解错误:const oscar = 'dog';,因为在这种情况下,它永远是一只狗,没有别的。但是,试想一下,如果巫师施展某种咒语,奥斯卡可能会变成一只猫:

let oscar: Animal = 'dog';

while(true) {
  switch (oscar) {
  case 'bird':
    ...
  case 'cat':
    ...
  case 'dog':
    console.log('bark');

    // here comes the wizard
    if(wizard.performsSpell('makeOscarBecomeACat')) {
      oscar = 'cat';  // that should be valid, because oscar is of type Animal
    }

    break;
  }
}

我是否误解了变量oscar 的赋值,或者这仅仅是一个 TypeScript 错误?

【问题讨论】:

    标签: javascript typescript switch-statement union-types


    【解决方案1】:

    您可能误解的是 TypeScript 2.0 及更高版本有一个名为control-flow based type analysis 的功能,在microsoft/TypeScript#8010 中实现。此功能的效果之一是

    S 类型的值赋值给T 类型的变量的赋值(包括声明中的初始化程序)将该变量的类型更改为T 缩小S 在分配之后的代码路径中。 [...] 由S 缩小的类型 T 计算如下: [...] 如果 T 是联合类型,则结果是每个组成类型的联合在 T 中,S 可以分配给它。

    这意味着声明

    let oscar: Animal = 'dog';
    

    被解释为:“变量oscar 的类型为Animal,一个联合类型。它被分配了一个字符串字面量类型"dog" 的值,所以在它被重新分配之前,我们将处理这个变量oscar 作为类型 Animal"dog" 缩小,即 "dog"

    因此在您的switch/case 声明中:

    case 'bird': // error!
    //   ~~~~~~ <-- Type '"bird"' is not comparable to type '"dog"'
    

    您在尝试将字符串文字 "bird" 与字符串文字 "dog" 进行比较时遇到错误。编译器知道'bird' 的情况是不可能的,因为您没有将oscar 重新分配给与'bird' 兼容的东西。

    即使在您的wizard 情况下,编译器也知道当它到达switch/case 语句时,oscar 只能是"cat""dog" 而不是"bird"

    case 'bird': // error! 
    //   ~~~~~~ <-- Type '"bird"' is not comparable to type '"cat" | "dog"'
    

    这可能都是好消息;编译器正在捕获永远不会发生的情况。在许多情况下,这些都是真正的错误。

    如果您不希望编译器意识到 oscar 绝对是 "dog" 并且只知道它是 Animal(例如,一个占位符,直到您编写的代码真正有可能成为任何Animal 的成员),您可以在作业本身中使用 type assertion

    let oscar: Animal = 'dog' as Animal;
    

    现在您的所有其他代码都将无错误地编译。您甚至可以忘记注释,因为它对您没有帮助:

    let oscar = 'dog' as Animal;
    

    好的,希望对您有所帮助;祝你好运!

    Playground link to code

    【讨论】:

    • 很好的答案,非常感谢您对窄联合类型的详细解释,这是我以前不知道的概念,但对我来说很有意义。
    猜你喜欢
    • 2019-04-01
    • 1970-01-01
    • 2021-11-10
    • 2012-09-13
    • 2016-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-11
    相关资源
    最近更新 更多