網羅性チェックの最小構成です。"on" と "off" を両方 case で処理していれば、default に届く値は存在しないので never への代入が成立します。まずは「全部処理した状態では default が never になる」という感覚をつかむための例です。
TypeScript
type Toggle = "on" | "off";
function toggleLabel(toggleState: Toggle): string {
switch (toggleState) {
case "on":
return "電源ON";
case "off":
return "電源OFF";
default: {
// 両方の case を処理済みなので、ここに来る値は存在しない
const unreachable: never = toggleState;
return unreachable;
}
}
}
console.log(toggleLabel("on"));
console.log(toggleLabel("off"));
2候補が増えたときにエラーで気づける
網羅性チェックの本当の価値は、ユニオンに候補を追加した瞬間に現れます。Season に新しい候補を足すと、case が足りないこの switch では never への代入がコンパイルエラーになり、直すべき場所を教えてくれます。「将来の変更に備えて仕込んでおく」チェックであることを示す例です。
TypeScript
// ここに "rainy" を追加すると、下の switch がコンパイルエラーになる
type Season = "spring" | "summer" | "autumn" | "winter";
function seasonComment(season: Season): string {
switch (season) {
case "spring":
return "暖かくなってきました";
case "summer":
return "暑さに注意です";
case "autumn":
return "過ごしやすい季節です";
case "winter":
return "防寒が必要です";
default: {
const unreachable: never = season; // 候補が増えるとここでエラー
return unreachable;
}
}
}
console.log(seasonComment("spring"));
console.log(seasonComment("winter"));
3判別可能ユニオンの網羅性チェック
リテラル型のユニオンだけでなく、kind で判別するオブジェクトのユニオンでも同じ定石が使えます。すべての kind を case で処理していれば default の shape は never になり、新しい図形を追加したときに計算の追加漏れを検出できます。状態やデータの種類が増えていく設計で特に効きます。
TypeScript
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number };
function areaOf(shape: Shape): number {
switch (shape.kind) {
case "circle":
return shape.radius * shape.radius * 3.14;
case "rect":
return shape.width * shape.height;
default: {
// kind をすべて処理していれば shape は never になっている
const unreachable: never = shape;
return unreachable;
}
}
}
console.log(areaOf({ kind: "circle", radius: 2 }));
console.log(areaOf({ kind: "rect", width: 3, height: 4 }));
4assertNever関数を共通部品にする
never への代入を毎回書く代わりに、never を受け取って必ず throw する assertNever 関数を1つ用意しておく書き方です。コンパイル時は引数が never かどうかで網羅を検査し、万一実行時に想定外の値が来た場合もエラーで止まります。プロジェクト共通のユーティリティとして定義されることが多い形です。
TypeScript
// 網羅できていれば、この関数の呼び出しはコンパイルが通る
function assertNever(value: never): never {
throw new Error("未処理の値です: " + value);
}
type PayMethod = "card" | "cash" | "point";
function feeRate(method: PayMethod): number {
switch (method) {
case "card":
return 0.03;
case "cash":
return 0;
case "point":
return 0.01;
default:
return assertNever(method); // 候補が増えるとこの行がエラーになる
}
}
console.log(feeRate("card"));
console.log(feeRate("point"));