如何检查 TypeScript 中的 switch 块是否详尽?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39419170/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 03:50:12  来源:igfitidea点击:

How do I check that a switch block is exhaustive in TypeScript?

typescript

提问by Ryan Cavanaugh

I have some code:

我有一些代码:

enum Color {
    Red,
    Green,
    Blue
}

function getColorName(c: Color): string {
    switch(c) {
        case Color.Red:
            return 'red';
        case Color.Green:
            return 'green';
        // Forgot about Blue
    }

    throw new Error('Did not expect to be here');
}

I forgot to handle the Color.Bluecase and I'd prefer to have gotten a compile error. How can I structure my code such that TypeScript flags this as an error?

我忘了处理这个Color.Blue案例,我宁愿得到一个编译错误。如何构建我的代码,以便 TypeScript 将此标记为错误?

回答by Ryan Cavanaugh

To do this, we'll use the nevertype (introduced in TypeScript 2.0) which represents values which "shouldn't" occur.

为此,我们将使用never表示“不应该”出现的值的类型(在 TypeScript 2.0 中引入)。

First step is to write a function:

第一步是写一个函数:

function assertUnreachable(x: never): never {
    throw new Error("Didn't expect to get here");
}

Then use it in the defaultcase (or equivalently, outside the switch):

然后在defaultcase 中使用它(或等效地,在 switch 之外):

function getColorName(c: Color): string {
    switch(c) {
        case Color.Red:
            return 'red';
        case Color.Green:
            return 'green';
    }
    return assertUnreachable(c);
}

At this point, you'll see an error:

此时,您会看到一个错误:

return assertUnreachable(c);
       ~~~~~~~~~~~~~~~~~~~~~
       Type "Color.Blue" is not assignable to type "never"

The error message indicates the cases you forgot to include in your exhaustive switch! If you left off multiple values, you'd see an error about e.g. Color.Blue | Color.Yellow.

错误消息表明您忘记包含在详尽开关中的情况!如果您遗漏了多个值,您会看到关于 eg 的错误Color.Blue | Color.Yellow

Note that if you're using strictNullChecks, you'll need that returnin front of the assertUnreachablecall (otherwise it's optional).

请注意,如果您正在使用strictNullChecks,则需要returnassertUnreachable调用前使用它(否则它是可选的)。

You can get a little fancier if you like. If you're using a discriminated union, for example, it can be useful to recover the discriminant property in the assertion function for debugging purposes. It looks like this:

如果你愿意,你可以变得更狂热。例如,如果您使用的是判别联合,则为了调试目的,在断言函数中恢复判别属性会很有用。它看起来像这样:

// Discriminated union using string literals
interface Dog {
    species: "canine";
    woof: string;
}
interface Cat {
    species: "feline";
    meow: string;
}
interface Fish {
    species: "pisces";
    meow: string;
}
type Pet = Dog | Cat | Fish;

// Externally-visible signature
function throwBadPet(p: never): never;
// Implementation signature
function throwBadPet(p: Pet) {
    throw new Error('Unknown pet kind: ' + p.species);
}

function meetPet(p: Pet) {
    switch(p.species) {
        case "canine":
            console.log("Who's a good boy? " + p.woof);
            break;
        case "feline":
            console.log("Pretty kitty: " + p.meow);
            break;
        default:
            // Argument of type 'Fish' not assignable to 'never'
            throwBadPet(p);
    }
}

This is a nice pattern because you get compile-time safety for making sure you handled all the cases you expected to. And if you do get a truly out-of-scope property (e.g. some JS caller made up a new species), you can throw a useful error message.

这是一个很好的模式,因为您可以获得编译时安全性,以确保您处理所有您期望的情况。如果你确实得到了一个真正超出范围的属性(例如,一些 JS 调用者组成了一个 new species),你可以抛出一个有用的错误消息。

回答by Marcelo Lazaroni

You don't need to use neveror add anything to the end of your switch.

您不需要使用never或添加任何内容到您的switch.

If

如果

  • Your switchstatement returns in each case
  • You have the strictNullCheckstypescript compilation flag turned on
  • Your function has a specified return type
  • The return type is not undefinedor void
  • 您的switch语句在每种情况下都会返回
  • 您已打开strictNullChecks打字稿编译标志
  • 您的函数具有指定的返回类型
  • 返回类型不是undefinedvoid

You will get an error if your switchstatement is non-exhaustive as there will be a case where nothing is returned.

如果您的switch语句不是详尽无遗的,您将收到错误消息,因为可能会出现不返回任何内容的情况。

From your example, if you do

从你的例子来看,如果你这样做

function getColorName(c: Color): string {
    switch(c) {
        case Color.Red:
            return 'red';
        case Color.Green:
            return 'green';
        // Forgot about Blue
    }
}

You will get the following compilation error:

您将收到以下编译错误:

Function lacks ending return statement and return type does not include undefined.

函数缺少结束 return 语句并且返回类型不包括undefined.

回答by TmTron

What I do is to define an error class:

我要做的是定义一个错误类:

export class UnreachableCaseError extends Error {
  constructor(val: never) {
    super(`Unreachable case: ${val}`);
  }
}

and then throw this error in the default case:

然后在默认情况下抛出此错误:

function meetPet(p: Pet) {
    switch(p.species) {
        case "canine":
            console.log("Who's a good boy? " + p.woof);
            break;
        case "feline":
            console.log("Pretty kitty: " + p.meow);
            break;
        default:
            // Argument of type 'Fish' not assignable to 'never'
            throw new UnreachableCaseError(dataType);
    }
}

I think it's easier to read because the throwclause has the default syntax highlighting.

我认为它更容易阅读,因为该throw子句具有默认的语法突出显示。

回答by Carlos Ginés

Building on top of Ryan's answer, I discovered herethat there is no need for any extra function. We can do directly:

基于 Ryan 的回答,我发现这里不需要任何额外的功能。我们可以直接做:

function getColorName(c: Color): string {
  switch (c) {
    case Color.Red:
      return "red";
    case Color.Green:
      return "green";
    // Forgot about Blue
    default:
      const _exhaustiveCheck: never = c;
      throw new Error("How did we get here?");
  }
}

You can see it in action herein TS Playground

您可以在行动中看到它在这里的TS游乐场

回答by drets

typescript-eslinthas "exhaustiveness checking in switch with union type"rule:
@typescript-eslint/switch-exhaustiveness-check

typescript-eslint具有“在具有联合类型的开关中进行详尽检查”规则:
@typescript-eslint/switch-exhaustiveness-check

回答by Brady Holt

Building on top of Ryanand Carlos'answers, you can use an anonymous method to avoid having to create a separate named function:

基于RyanCarlos 的回答,您可以使用匿名方法来避免创建单独的命名函数:

function getColorName(c: Color): string {
  switch (c) {
    case Color.Red:
      return "red";
    case Color.Green:
      return "green";
    // Forgot about Blue
    default:
      ((x: never) => {
        throw new Error(`${c} was unhandled!`);
      })(c);
  }
}

If your switch is not exhaustive, you'll get a compiletime error.

如果您的开关不是详尽无遗,您将收到编译时错误。

回答by Hadrien Milano

As a nice twist on Ryan's answer, you can replace neverwith an arbitrary string to make the error message more user friendly.

作为 Ryan 答案的一个很好的转折,您可以never用任意字符串替换,以使错误消息更加用户友好。

function assertUnreachable(x: 'error: Did you forget to handle this type?'): never {
    throw new Error("Didn't expect to get here");
}

Now, you get:

现在,你得到:

return assertUnreachable(c);
       ~~~~~~~~~~~~~~~~~~~~~
       Type "Color.Blue" is not assignable to type "error: Did you forget to handle this type?"

This works because nevercan be assigned to anything, including an arbitrary string.

这是有效的,因为never可以分配给任何东西,包括任意字符串。

回答by Alendorff

In really simple cases when you just need to return some string by enum value it's easier (IMHO) to use some constant to store dictionary of results instead of using switch. For example:

在非常简单的情况下,当您只需要通过枚举值返回一些字符串时,使用一些常量来存储结果字典而不是使用 switch 更容易(恕我直言)。例如:

enum Color {
    Red,
    Green,
    Blue
}

function getColorName(c: Color): string {
  const colorNames: Record<Color, string> = {
    [Color.Red]: `I'm red`,
    [Color.Green]: `I'm green`,
    [Color.Blue]: `I'm blue, dabudi dabudai`,   
  }

  return colorNames[c] || ''
}

So here you will have to mention every enum value in constant, otherwise you get an error like, for example, if Blue is missing:

所以在这里你必须提到常量中的每个枚举值,否则你会得到一个错误,例如,如果缺少 Blue:

TS2741: Property 'Blue' is missing in type '{ [Color.Red]: string; [Color.Green]: string;' but required in type 'Record'.

TS2741:类型 '{ [Color.Red]: string; 中缺少属性 'Blue' [Color.Green]: 字符串;' 但在“记录”类型中需要。

However it's often not the case and then it's really better to throw an error just like Ryan Cavanaugh proposed.

然而,通常情况并非如此,然后像 Ryan Cavanaugh 提出的那样抛出错误确实更好。

Also I was a bit upset when found that this won't work also:

当发现这也不起作用时,我也有点沮丧:

function getColorName(c: Color): string {
    switch(c) {
        case Color.Red:
            return 'red';
        case Color.Green:
            return 'green';
    }
    return '' as never // I had some hope that it rises a type error, but it doesn't :)
}

回答by sky

Create a custom function instead of using a switchstatement.

创建自定义函数而不是使用switch语句。

export function exhaustSwitch<T extends string, TRet>(
  value: T,
  map: { [x in T]: () => TRet }
): TRet {
  return map[value]();
}

Example usage

示例用法

type MyEnum = 'a' | 'b' | 'c';

const v = 'a' as MyEnum;

exhaustSwitch(v, {
  a: () => 1,
  b: () => 1,
  c: () => 1,
});

If you later add dto MyEnum, you will receive an error Property 'd' is missing in type ...

如果您稍后添加dMyEnum,您将收到一个错误Property 'd' is missing in type ...