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 链,它是一种可读性更好的替代写法。