neverによる網羅性チェック
neverを使って、未処理のケースをコンパイル時に検出します。
「neverによる網羅性チェック」はCoddyKit上の無料TypeScript Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはTypeScript Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 TypeScript Academyコースには全4レッスンが含まれています。
網羅性の問題
新しいユニオンメンバーを追加すると、どこかでそのメンバーの処理を忘れやすくなります。網羅性チェックを使えば、その見落としをコンパイルエラーに変えられます。
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
// If we add "triangle" later, we want every switch to complain.never型
never型は、決して発生しない値を表します。すべてのcaseを処理すると、defaultブランチに到達する値の型はneverになります。
function fail(): never {
throw new Error("unreachable");
}
// never is assignable to nothing except never itself.default caseでneverに代入する
defaultブランチで、値をnever型の変数に代入します。すべてのバリアントを処理していれば代入はコンパイルできますが、処理していないものがあればエラーになります。
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "square": return s.side ** 2;
default:
const _exhaustive: never = s;
return _exhaustive;
}
}
console.log(area({ kind: "square", side: 3 }));caseを忘れるとどうなるか
triangleメンバーを追加したのにそのcaseを忘れると、default内のsはもはやneverではないため、代入がコンパイル時に失敗します。
// type Shape = ... | { kind: "triangle"; base: number; height: number };
// Now in default, s is { kind: "triangle"; ... }
// const _exhaustive: never = s; // Error: triangle not assignable to neverassertNeverヘルパー
再利用可能なassertNever関数にこのパターンを集約できます。この関数はneverを受け取って例外をスローし、そのブランチには到達しないはずであることを明示します。
function assertNever(value: never): never {
throw new Error("Unhandled case: " + JSON.stringify(value));
}
console.log(typeof assertNever);switchでassertNeverを使う
default caseでassertNever(s)を呼び出します。これによりコンパイル時に網羅性が強制され、到達した場合には明確な実行時エラーが発生します。
function assertNever(value: never): never {
throw new Error("Unhandled: " + JSON.stringify(value));
}
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "square": return s.side ** 2;
default: return assertNever(s);
}
}
console.log(area({ kind: "circle", radius: 1 }).toFixed(2));コンパイル時の安全性と実行時の安全性
neverによるチェックは、コードを実行する前に未処理のcaseを検出します。また、型チェックをすり抜けた場合でも、スローされたエラーが実行時に保護してくれます。
function assertNever(x: never): never {
throw new Error("Unhandled: " + String(x));
}
// Compile error if a case is missing; runtime throw as a backstop.
console.log("two layers of safety");defaultなしの網羅性
関数に明示的な戻り値の型があり、switchですべてのcaseを処理している場合、TypeScriptはreturnの不足も指摘できます。これも網羅性を確保する方法の1つです。
type Light = "red" | "yellow" | "green";
function next(l: Light): Light {
switch (l) {
case "red": return "green";
case "yellow": return "red";
case "green": return "yellow";
}
// No default needed; all cases return.
}
console.log(next("red"));if/elseチェーンでの網羅性
同じ考え方はif/elseにも適用できます。各バリアントを処理した後、最後のelseはnever型の値を受け取ります。
function assertNever(x: never): never { throw new Error("bad"); }
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function name(s: Shape): string {
if (s.kind === "circle") return "circle";
else if (s.kind === "square") return "square";
else return assertNever(s);
}
console.log(name({ kind: "square", side: 2 }));neverが適切なツールである理由
neverは何にも代入できないため、残ったバリアントがあると代入が失敗します。そのため、neverは未処理のcaseを検出するのに最適です。
// Only never is assignable to never.
let x: never;
// x = "hi"; // Error
// Any concrete leftover type fails the same way.
console.log("never catches gaps");リファクタリングの安全網としての網羅性
あらゆる場所でassertNeverを使っておけば、ユニオンメンバーを追加したときに、更新が必要な箇所を正確に示すコンパイルエラーの一覧が得られます。
function assertNever(x: never): never { throw new Error("unhandled"); }
type Status = "idle" | "busy";
function render(s: Status): string {
switch (s) {
case "idle": return "Idle";
case "busy": return "Busy";
default: return assertNever(s);
}
}
console.log(render("idle"));クイックチェック:網羅性
網羅性チェックについての理解度を確認しましょう。
まとめ:neverによる網羅性
defaultの値をneverに代入するか、assertNeverに渡すことで、コンパイラにすべてのバリアントの処理を強制させる方法を学びました。これにより、処理を忘れたcaseがコンパイルエラーになります。
function assertNever(x: never): never { throw new Error("unhandled"); }
type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
function f(s: Shape) {
switch (s.kind) {
case "circle": return s.radius;
case "square": return s.side;
default: return assertNever(s);
}
}
console.log(f({ kind: "circle", radius: 5 }));よくある質問
「neverによる網羅性チェック」レッスンは無料ですか?
はい。「neverによる網羅性チェック」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、TypeScript Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 TypeScript Academyコースには全4レッスンが含まれています。
「neverによる網羅性チェック」で何を学びますか?
neverを使って、未処理のケースをコンパイル時に検出します。 ブラウザで直接実行するハンズオンコードでTypeScript Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
TypeScript Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのTypeScript Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「neverによる網羅性チェック」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このTypeScript Academyレッスンでコードを書いて実行できますか?
はい。すべてのTypeScript Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 判別可能なユニオンの構築
- 判別プロパティによる型の絞り込み
- neverによる網羅性チェック
- ステートマシンのモデル化