Kodokon kodokon.com

リテラル型と判別可能なユニオン

コンパイラが代わりにチェックしてくれるユニオンで、アプリケーションの状態をモデル化しましょう。

8 分 · 3 問

このレッスンを Kodokon で開く

リテラル型は値を厳密な内容に制限します。string ではなく 'info' です。誤用しようがない API の土台となるもので、コンパイラはリスト外のあらゆる値を拒否し、エディタは自動補完を提供してくれます。

TYPESCRIPT
type Level = "info" | "warn" | "error";

function log(level: Level, message: string): void {
  console.log(`[${level}] ${message}`);
}

log("info", "Server started");
// log("fatal", "Boom"); -> rejected by the compiler
リテラルのユニオンは許可される値を固定する

次のステップは、共通のリテラルプロパティ(判別子と呼ばれます)を共有するオブジェクト型を組み合わせることです。たとえばネットワーク呼び出しの各状態は、それ自身に属するデータだけを持ちます。読み込み中に data へアクセスすることは、そもそも不可能なのです。

TYPESCRIPT
type ApiState =
  | { status: "loading" }
  | { status: "success"; data: string[] }
  | { status: "error"; message: string };

function render(state: ApiState): string {
  switch (state.status) {
    case "loading":
      return "Loading...";
    case "success":
      return state.data.join(", ");
    case "error":
      return state.message;
  }
}
status プロパティで判別されるユニオン

case の中で、TypeScript は型を絞り込みますcase 'error' のあとでは state.message が存在し、state.data は存在しません。ナローイングと呼ばれるこの仕組みは、壊れやすい手動チェックに取って代わります。網羅性ガードを加えて、全体をしっかり固めましょう。

TYPESCRIPT
function assertNever(value: never): never {
  throw new Error("Unhandled case: " + String(value));
}

function label(state: ApiState): string {
  switch (state.status) {
    case "loading": return "In progress";
    case "success": return "Done";
    case "error": return "Failed";
    default: return assertNever(state);
  }
}
never 型による網羅性ガード

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. 判別可能なユニオンにおいて、status のような共有プロパティの役割は何ですか?
    • 実行時のパフォーマンスを向上させる
    • 各分岐でコンパイラが型を絞り込めるようにする
    • 受け取った JSON でそのプロパティを必須にする
    • プロパティの重複を防ぐ
  2. type Level = 'info' | 'warn' のとき、log(level, msg) のどの呼び出しがコンパイルできますか?
    • log('debug', msg)
    • log('info', msg)
    • log(someString, msg)(someString は string 型)
  3. switch の default 分岐にある assertNever(value: never) 関数の目的は何ですか?
    • 本番環境でエラーをログに記録する
    • case の忘れをコンパイルエラーに変える
    • 値を never に変換する
    • プログラムをより速く終了させる