如何检查 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
How do I check that a switch block is exhaustive in 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.Blue
case 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 never
type (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 default
case (or equivalently, outside the switch):
然后在default
case 中使用它(或等效地,在 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 return
in front of the assertUnreachable
call (otherwise it's optional).
请注意,如果您正在使用strictNullChecks
,则需要return
在assertUnreachable
调用前使用它(否则它是可选的)。
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 never
or add anything to the end of your switch
.
您不需要使用never
或添加任何内容到您的switch
.
If
如果
- Your
switch
statement returns in each case - You have the
strictNullChecks
typescript compilation flag turned on - Your function has a specified return type
- The return type is not
undefined
orvoid
- 您的
switch
语句在每种情况下都会返回 - 您已打开
strictNullChecks
打字稿编译标志 - 您的函数具有指定的返回类型
- 返回类型不是
undefined
或void
You will get an error if your switch
statement 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 throw
clause 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-eslint
has "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:
基于Ryan和Carlos 的回答,您可以使用匿名方法来避免创建单独的命名函数:
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 never
with 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 never
can 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 switch
statement.
创建自定义函数而不是使用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 d
to MyEnum
, you will receive an error Property 'd' is missing in type ...
如果您稍后添加d
到MyEnum
,您将收到一个错误Property 'd' is missing in type ...