Menu

TypeScript switch case:类型收窄与穷尽检查

TypeScript 中的 switch 语句:语法、每个 case 如何收窄联合类型、漏掉 case 时会编译失败的穷尽式 switch、switch (true) 写法、贯穿(fallthrough)以及块级作用域。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

TypeScript 的 switch 语句就是带类型检查的 JavaScript switch。它用 === 把一个值依次与每个 case 比较,执行匹配的分支,遇到 break 或 return 时停止。TypeScript 会在每个 case 内部收窄被比较的值,还能检查是否处理了每一种可能的值。

叠加的标签(case "sat": case "sun":)共用一个分支。return 会结束函数,所以它后面不需要 break。

语法

switch (expression) {
    case value1:
        // runs when expression === value1
        break;
    case value2:
    case value3:
        // runs for value2 or value3
        break;
    default:
        // runs when nothing else matched
}
  • 匹配使用严格相等 ===:case 1 不会匹配字符串 "1"。
  • 如果没有 break、return 或 throw,执行会继续进入下一个 case(贯穿)。
  • default 是可选的,可以放在任何位置,不过惯例是放在最后。
  • TypeScript 能证明永远不会匹配的 case 值会报错 TS2678。对于类型为 "a" | "b" 的参数,case "c": 会报告 Type '"c"' is not comparable to type '"a" | "b"'.,这能发现 case 标签中的拼写错误。

在每个 case 中收窄类型

在每个 case 中,TypeScript 都知道匹配的是哪个值,并据此收窄类型。这在可辨识联合类型上最有用:对共享的标签属性做 switch,每个 case 看到的就是匹配的那个变体及其自身的属性。

shape.radius 只有在 "circle" 这个 case 中才能通过编译。在它之外,shape 可能是矩形,而矩形没有 radius。这里没有 default,函数的返回类型依然能通过 number 的类型检查,因为 TypeScript 看得出这三个 case 覆盖了所有 kind。基于这种写法的更多模式,请看 可辨识联合类型 一文。

用 never 实现穷尽式 switch

如果新增了第四种形状却没有对应的 case,上面的函数就会编译失败(TS2366,缺少返回值)。但这只在函数有返回值时才管用。想要一种在 void 代码中也有效、并且错误信息更清楚的保证,就添加一个把值赋给 never 的 default:

index.ts(14,19): error TS2322: Type '{ kind: "triangle"; base: number; height: number; }' is not assignable to type 'never'.

处理完已有的 case 后,shape 唯一剩下的类型就是三角形变体,而它不能赋值给 never。错误信息准确地指出了缺少的是什么。加上 case "triangle": return (shape.base * shape.height) / 2;,default 看到的就是 never,于是代码块能够编译并打印 9。throw 仍然能在运行时防范错误数据,比如来自 JSON、类型没有预料到的 kind。

很多代码库会把这个检查封装成一个辅助函数:

同样的技巧也适用于枚举:对枚举值做 switch,并在 default 中把它传给 assertNever。

用 switch (true) 处理范围和条件

switch 比较的是值,所以处理范围需要一个技巧:对 true 做 switch,并在每个 case 中写一个条件。从 TypeScript 5.3 开始,这些条件会像 if 语句一样收窄类型。

各个 case 按顺序测试,所以要把最具体的条件放在最前面。它和 if / else if 哪个更好读,见仁见智;两者的行为是一样的。

贯穿与 break

有代码却没有 break、return 或 throw 的 case 会贯穿到下一个 case。这几乎总是 bug。编译选项 noFallthroughCasesInSwitch(不属于 strict)会把它变成错误 TS7029:Fallthrough case in switch.,同时仍然允许叠加空标签。

像这样有意的贯穿是可以运行的,但开启 noFallthroughCasesInSwitch 后就必须改写,比如改成多个独立的 if 判断。这样通常也更清楚。

case 中的变量

整个 switch 体是一个代码块,所以在一个 case 中声明的 const 在其他 case 中也可见(重复声明会报错)。用花括号把 case 包起来,就能给它一个独立的作用域:

如果没有花括号,第二个 const unit 会导致编译错误 TS2451:Cannot redeclare block-scoped variable 'unit'.

switch 与对象查找

当每个 case 只是把一个值映射成另一个值时,用 Record 声明类型的对象更简短,而且 TypeScript 会检查每个键都存在:

漏掉某个键会导致编译错误,这和 never 检查一样能保证穷尽。如果各个 case 要执行不同的逻辑、收窄联合类型的变体或者提前返回,就继续用 switch。

常见问题

TypeScript 中如何写 switch 语句?

和 JavaScript 完全一样:switch (value) { case "a": ...; break; default: ... }。每个 case 都用 === 比较。TypeScript 额外做了检查:永远不可能与被比较类型匹配的 case 值会报错(TS2678),而在每个 case 内部,被比较的变量会被收窄为该 case 的类型。

TypeScript 中如何让 switch 做穷尽检查?

添加一个 default,把值赋给一个类型为 never 的变量:default: { const unreachable: never = value; throw new Error(...) }。当联合类型的每个成员都处理过时,值在这里就是 never,代码可以编译。漏掉某个成员时,编译器会报告缺少的成员(TS2322)。

TypeScript switch 中如何让多个 case 执行同一段代码?

把多个标签叠在一起,中间不写代码:case "sat": case "sun": return "weekend";。空的 case 会贯穿到下一个 case。noFallthroughCasesInSwitch 选项只会报告那些有代码却没有 break 或 return 的 case,所以叠加的标签仍然是允许的。

switch (true) 在 TypeScript 中会收窄类型吗?

会,从 TypeScript 5.3 开始。在 switch (true) { case typeof x === "string": ... } 中,x 在这个 case 内部会被收窄为 string,就像在 if 中一样。对于带有范围判断或类型判断的 if/else if 链,它是一种可读性更好的替代写法。

Coddy 编程语言插图

和 Coddy 一起学编程

开始