我有一些代码:
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');
}
Run Code Online (Sandbox Code Playgroud)
我忘了处理这个Color.Blue案子,我宁愿遇到编译错误.如何构造我的代码,以便TypeScript将此标记为错误?
Rya*_*ugh 80
为此,我们将使用never类型(在TypeScript 2.0中引入),它表示"不应该"出现的值.
第一步是编写一个函数:
function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}
Run Code Online (Sandbox Code Playgroud)
然后在default案例中使用它(或等效地,在交换机外部):
function getColorName(c: Color): string {
switch(c) {
case Color.Red:
return 'red';
case Color.Green:
return 'green';
}
return assertUnreachable(c);
}
Run Code Online (Sandbox Code Playgroud)
此时,您将看到一个错误:
return assertUnreachable(c);
~~~~~~~~~~~~~~~~~~~~~
Type "Color.Blue" is not assignable to type "never"
Run Code Online (Sandbox Code Playgroud)
错误消息表示您忘记包含在详尽开关中的情况!如果你没有多个值,你会看到一个错误,例如Color.Blue | Color.Yellow.
请注意,如果您正在使用strictNullChecks,则需要return在assertUnreachable通话前使用它(否则它是可选的).
如果你愿意,你可以得到一点点发烧友.例如,如果您正在使用区分联合,则可以在断言函数中恢复判别属性以进行调试.它看起来像这样:
// 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);
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个很好的模式,因为您可以获得编译时的安全性,以确保您处理了预期的所有情况.如果你确实获得了一个真正的超出范围的属性(例如,一些JS调用者组成了一个新的species),你可以抛出一个有用的错误信息.
Car*_*nés 36
基于 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(`Unhandled color case: ${exhaustiveCheck}`);
}
}
Run Code Online (Sandbox Code Playgroud)
您可以在行动中看到它在这里的TS游乐场
编辑:包括避免“未使用的变量”短绒消息的建议。
huw*_*huw 36
satisfies在 TypeScript 4.9 中,使用关键字可以更容易地实现这一点。
enum Color {\n Red,\n Green,\n Blue\n}\n\nfunction getColorName(c: Color): string {\n switch(c) {\n case Color.Red:\n return \'Red\';\n case Color.Green:\n return \'Green\';\n case Color.Blue:\n return \'Blue\';\n default:\n return c satisfies never;\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n如果您的检查很详尽,c则应始终为 类型never。我们使用关键字将 \xe2\x80\x98assert\xe2\x80\x99 传递给编译器satisfies(实质上,告诉它c应该可分配给never, ,否则会出错)。如果将来向枚举添加新的情况,您将得到一个纯粹的编译时错误。
在底层,该default分支将编译为:
default:\n return c;\nRun Code Online (Sandbox Code Playgroud)\n这是一个文字表达式,只会计算c. 这应该\xe2\x80\x99t对你的代码有影响,但是如果c是,例如,一个类的getter,它将评估默认分支是否曾经运行并可能产生副作用(因为它会在当前接受的回答)。
dre*_*ets 16
typescript-eslint具有“在具有联合类型的开关中进行详尽检查”规则:
@typescript-eslint/switch-exhaustiveness-check
要配置它,请启用规则package.json并启用 TypeScript 解析器。一个适用于 React 17 的示例:
"eslintConfig": {
"extends": "react-app",
"rules": {
"@typescript-eslint/switch-exhaustiveness-check": "warn"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
}
Run Code Online (Sandbox Code Playgroud)
TmT*_*ron 12
我所做的是定义一个错误类:
export class UnreachableCaseError extends Error {
constructor(val: never) {
super(`Unreachable case: ${val}`);
}
}
Run Code Online (Sandbox Code Playgroud)
然后在默认情况下抛出此错误:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
我认为它更容易阅读,因为该throw子句具有默认语法高亮.
Mar*_*oni 11
您无需never在末尾使用或添加任何内容switch。
如果
switch陈述在每种情况下都会返回strictNullChecks打字稿编译标记undefined或void如果您的switch陈述不够详尽,则会出现错误,因为在某些情况下不会返回任何内容。
从您的示例中,如果您这样做
function getColorName(c: Color): string {
switch(c) {
case Color.Red:
return 'red';
case Color.Green:
return 'green';
// Forgot about Blue
}
}
Run Code Online (Sandbox Code Playgroud)
您将收到以下编译错误:
函数缺少结尾的return语句,并且返回类型不包含
undefined。
小智 7
作为 Ryan 答案的一个很好的转折,您可以never用任意字符串替换,以使错误消息更加用户友好。
function assertUnreachable(x: 'error: Did you forget to handle this type?'): never {
throw new Error("Didn't expect to get here");
}
Run Code Online (Sandbox Code Playgroud)
现在,你得到:
return assertUnreachable(c);
~~~~~~~~~~~~~~~~~~~~~
Type "Color.Blue" is not assignable to type "error: Did you forget to handle this type?"
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为never可以分配给任何东西,包括任意字符串。
基于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(`${x} was unhandled!`);
})(c);
}
}
Run Code Online (Sandbox Code Playgroud)
如果您的开关不是详尽无遗,您将收到编译时错误。
| 归档时间: |
|
| 查看次数: |
10164 次 |
| 最近记录: |