简体   繁体   English

您如何使用引用其他字符串枚举的字符串枚举在 Typescript 中进行详尽的 switch case

[英]How do you do an exhaustive switch case in Typescript with string enums that refer to other string enums

I have a string enum that is a subset of the values from another string enum.我有一个字符串枚举,它是另一个字符串枚举值的子集。 I am trying to do an exhaustive switch case across the former.我正在尝试对前者进行详尽的切换案例。

This is a contrived example of the construct I am attempting to represent in Typescript这是我试图在 Typescript 中表示的构造的一个人为示例

const enum Color {
    BLUE = 'Blue',
    YELLOW = 'Yellow',
    RED = 'Red'
}

const enum SupportedColor {
    BLUE = Color.BLUE,
    YELLOW = Color.YELLOW
}


function doSomething(colorFromOutsideInput: string): boolean {
    const supportedColor: SupportedColor = (colorFromOutsideInput as unknown) as SupportedColor;
    switch (supportedColor) {
        case SupportedColor.BLUE:
            return true;
        case SupportedColor.YELLOW:
            return true;
        default:
            return invalidColor(supportedColor);
    }
}

function invalidColor(supportedColor: never): never {
    throw new Error();
}

In this code, the default in the switch case fails compilation with在这段代码中, switch case 中的默认值编译失败

Argument of type 'SupportedColor' is not assignable to parameter of type 'never'.

If I were to do this switch case across the Color enum instead of SupportedColor enum, it would work.如果我要在Color枚举而不是SupportedColor枚举中执行此切换案例,它将起作用。

Based on reading in the typescript docs, I see no evidence of why the compiler treats the two enums differently because they evaluate to the same strings.基于对打字稿文档的阅读,我没有看到编译器为什么会以不同的方式对待这两个枚举的证据,因为它们评估为相同的字符串。

I'm going to minimize the example a bit but the issues raised are the same.我会尽量减少这个例子,但提出的问题是一样的。


Enums where each enum value is of an explicit string literal or numeric literal type act like union types , which can be narrowed via type guards .每个枚举值都是显式字符串文字数字文字类型的枚举的行为类似于联合类型,可以通过类型保护来缩小范围。 This sort of enum is called a union enum :这种枚举称为联合枚举

// union enum
const enum SupportedColor {
    BLUE = "Blue",
    YELLOW = "Yellow"
}

// no error here
function doSomething(supportedColor: SupportedColor): boolean {
    switch (supportedColor) {
        case SupportedColor.BLUE:
            supportedColor // SupportedColor.BLUE
            return true;
        case SupportedColor.YELLOW:
            supportedColor // SupportedColor.YELLOW
            return true;
    }
    //supportedColor // never
    //~~~~~~~~~~~~~ <-- unreachable code
}

In the above code, the compiler recognizes that the switch statement is exhaustive because supportedColor of type SupportedColor can be narrowed by control flow anlaysis.在上面的代码时,编译器识别出该switch语句是穷举因为supportedColor类型的SupportedColor可以通过控制流anlaysis变窄。 The type SupportedColor is equivalent to the union SupportedColor.BLUE | SupportedColor.YELLOW SupportedColor类型等价于联合SupportedColor.BLUE | SupportedColor.YELLOW SupportedColor.BLUE | SupportedColor.YELLOW . SupportedColor.BLUE | SupportedColor.YELLOWSupportedColor.BLUE | SupportedColor.YELLOW

So inside the various case s, supportedColor is narrowed in turn to the type SupportedColor.BLUE and SupportedColor.YELLOW .因此,在各种casesupportedColor依次缩小为SupportedColor.BLUESupportedColor.YELLOW类型。 After the switch statement is over, the compiler knows that supportedColor is of type never , since both SupportedColor.BLUE and SupportedColor.YELLOW have been filtered out of the union. switch语句结束后,编译器知道supportedColor的类型为never ,因为SupportedColor.BLUESupportedColor.YELLOW已从联合中过滤掉。 If you uncomment the code after the switch block, the compiler will even complain that it is unreachable in TS3.7+.如果在switch块之后取消注释代码,编译器甚至会抱怨它在 TS3.7+ 中无法访问。

Therefore all code paths return a value and the compiler does not complain.因此所有代码路径都返回一个值,编译器不会抱怨。


Now consider what happens when you change the enum values to be calculated instead of literals:现在考虑当您更改要计算的枚举值而不是文字时会发生什么:

const enum Color {
    BLUE = 'Blue',
    YELLOW = 'Yellow',
    RED = 'Red'
}

// calculated enum
const enum SupportedColor {
    BLUE = Color.BLUE,
    YELLOW = Color.YELLOW
}

function doSomething(supportedColor: SupportedColor): boolean { // error!
    // -------------------------------------------->  ~~~~~~~
    // function lacks ending return statement
    switch (supportedColor) {
        case SupportedColor.BLUE:
            supportedColor // SupportedColor
            return true;
        case SupportedColor.YELLOW:
            supportedColor // SupportedColor
            return true;
    }
    supportedColor // SupportedColor
}

Here the enum type SupportedColor is no longer considered to be a union type.这里枚举类型SupportedColor不再被视为联合类型。 Calculated enums are not union enums .计算的枚举不是联合枚举 And therefore the compiler has no way to narrow or filter the type of supportedColor in or after the switch statement.因此,编译器无法缩小或过滤switch语句中或之后的supportedColor类型。 The type stays SupportedColor the whole way through.类型在整个过程中保持SupportedColor And the switch statement isn't seen to be exhaustive, and the compiler complains that the function doesn't always return a value.并且switch语句并不详尽,编译器抱怨该函数并不总是返回一个值。


So that explains what's going on, and the documentation link for union enums does say that union enums and calculated enums are different.这解释了发生了什么,并且联合枚举的文档链接确实说联合枚举和计算枚举是不同的。 So it's documented.所以它被记录在案。 But what isn't explained in the documentation is why calculated enums are not treated as unions.但是文档中没有解释的是为什么计算的枚举不被视为联合。 That is: it's working as designed, but does anyone actually prefer it to be this way, or is it just a design limitation?也就是说:它按设计工作,但是否有人真的更喜欢这样,还是只是设计限制?

The closest thing I can find to a canonical answer is the GitHub issue microsoft/TypeScript#22709 referencing some meeting notes inside microsoft/TypeScript#26241 .我能找到的最接近规范答案的是 GitHub 问题microsoft/TypeScript#22709引用了microsoft/TypeScript#26241 中的一些会议笔记。 These notes are:这些笔记是:

  • There are actually many kinds of enums under the hood引擎盖下实际上有很多种枚举
  • Union enums only get created when only bare literals (or nothing) are initializers仅当只有裸文字(或没有)是初始值设定项时,才会创建联合枚举
  • These kinds of enums behave differently这些类型的枚举行为不同
  • You might want one or the other and this is how you choose您可能想要其中一个,这就是您的选择
  • Working As Intended;按预期工作; would be a Breaking Change将是一个突破性的变化

So the TS team says that non-union enums are actually sometimes desirable and that making a change here would break existing code which relies on the current behavior.因此 TS 团队表示,非联合枚举实际上有时是可取的,并且在此处进行更改会破坏依赖于当前行为的现有代码。 It's not particularly satisfying of an answer since it doesn't describe the use cases in which people want non-union enums, but it's an answer nonetheless: this is intentional.它不是特别令人满意的答案,因为它没有描述人们想要非联合枚举的用例,但它仍然是一个答案:这是故意的。 If you want union enums, use literals and not calculated values.如果您想要联合枚举,请使用文字而不是计算值。


Okay, hope that helps;好的,希望有帮助; good luck!祝你好运!

Playground link to code Playground 链接到代码

I'm not sure that I understand the intention of the example, but consider changing the signature of invalidColor to receive type unknown :我不确定我是否理解该示例的意图,但请考虑更改invalidColor的签名以接收类型unknown

function invalidColor(supportedColor: unknown): never {
    throw new Error();
}

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

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