TypeScriptの switch 文は、型チェックが付いたJavaScriptの switch です。値を各 case と === で比較し、一致した分岐を実行して、break か return で止まります。TypeScriptは各 case の中で switch した値を絞り込み、ありうる値がすべて処理されているかをチェックすることもできます。
重ねたラベル(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 はありませんが、3つの case がすべての kind を網羅していることをTypeScriptがわかっているので、関数は number を返すものとして型チェックを通ります。これをもとにしたパターンは判別可能なユニオンのページにあります。
never を使った網羅的な switch
上の関数は、4つ目の図形を case なしで追加するとコンパイルできなくなります(TS2366、return がない)。ただしこれは関数が値を返す場合にしか働きません。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 のような、実行時の不正なデータへの備えとして残しておきます。
多くのコードベースではこのチェックをヘルパー関数にまとめています:
同じ方法は enum にも使えます。enum の値で switch し、default でそれを assertNever に渡します。
範囲や条件には switch (true)
switch は値を比較するので、範囲を扱うにはひと工夫が必要です。true で switch し、各 case に条件を書きます。TypeScript 5.3 からは、その条件も if 文と同じように型を絞り込みます。
case は順番に評価されるので、いちばん具体的な条件を先に置きます。if と else if より読みやすいかどうかは好みの問題で、動作は同じです。
フォールスルーと break
コードがあって break、return、throw のない case は、次の case にフォールスルーします。これはほとんどの場合バグです。コンパイラーオプション noFallthroughCasesInSwitch(strict には含まれません)を有効にするとこれがエラー TS7029 Fallthrough case in switch. になり、空のラベルを重ねることは引き続き許されます。
このような意図的なフォールスルーも動きますが、noFallthroughCasesInSwitch を有効にしている場合は、たとえば別々の if によるチェックに書き換える必要があります。そのほうがたいていわかりやすくなります。
case の中の変数
switch の本体全体がひとつのブロックなので、ある case で宣言した const はほかの case からも見え、同じ名前で再宣言するとエラーになります。case を波かっこで囲めば、独自のスコープを持たせられます:
波かっこがないと、2つ目の const unit はコンパイルエラー TS2451 Cannot redeclare block-scoped variable 'unit'. になります。
switch とオブジェクトによる対応表
各 case が値を別の値に対応させるだけなら、Record で型付けしたオブジェクトのほうが短く書け、TypeScriptはすべてのキーがそろっているかをチェックします:
キーが欠けるとコンパイルエラーになるので、never によるチェックと同じ網羅性が得られます。switch は、case ごとに違う処理をする場合、ユニオンのバリアントを絞り込む場合、早期に return する場合に使いましょう。
よくある質問
TypeScriptで switch 文を書くには?
JavaScriptとまったく同じです: switch (value) { case "a": ...; break; default: ... }。case は === で比較されます。TypeScriptはチェックを加えます。switch する値の型と決して一致しない case の値はエラー(TS2678)になり、各 case の中では switch した変数がその case に絞り込まれます。
TypeScriptで switch を網羅的にするには?
値を never 型の変数に代入する default を追加します: 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 だけなので、重ねたラベルはそのまま使えます。
TypeScriptの switch (true) で型は絞り込まれますか?
TypeScript 5.3 から絞り込まれます。switch (true) { case typeof x === "string": ... } では、その case の中で x が if と同じように string に絞り込まれます。範囲や型のチェックを if と else if で連ねる代わりに使える、読みやすい書き方です。