Kodokon kodokon.com

Narrowing: typeof、in、instanceof、そして型ガード

型の絞り込み(narrowing)の仕組みを習得し、独自の述語を書いてユニオンをより信頼できるものにします。

10 分 · 3 問

このレッスンを Kodokon で開く

Narrowing(絞り込み) とは、TypeScript が制御フローを解析することで、広い型(ユニオンや unknown)をより正確な型へと洗練させていく仕組みです。あなたは意識せずにすでにこれを使っています。型付きの値に対するすべての if が解析を引き起こすからです。コンパイラは組み込みのガードを 3 系統追跡します。プリミティブに対する typeof、クラスインスタンスに対する instanceof、プロパティの存在を調べる in です。これらを適切に選ぶことで、検証を導くどころか無効にしてしまう as による無謀なキャストを避けられます。

TYPESCRIPT
function formatValue(
  value: string | number | Date
): string {
  if (typeof value === "string") {
    return value.trim();
  }
  if (value instanceof Date) {
    return value.toISOString();
  }
  return value.toFixed(2);
}
それぞれの分岐が型を絞り込み、最後の分岐は number にしかなり得ません。

in 演算子は、判別用のフィールドを持たない構造的に異なるオブジェクトを扱うときにとても役立ちます。ただし注意が必要です。in はキーの「存在」を(継承されたものも含めて)テストするのであって、その型をテストするわけではありません。外部データ(API や JSON)に対しては、実行時に実際に存在するものではなく、あなたの宣言に従って絞り込みます。現場での典型的な落とし穴は、typeof value === "object" のチェックが null を通してしまうことです。JavaScript では typeof null"object" だからです。必ず明示的な null チェックを追加しましょう。

TYPESCRIPT
interface Cat { meow(): void }
interface Dog { bark(): void }

function speak(animal: Cat | Dog): void {
  if ("meow" in animal) {
    animal.meow();
  } else {
    animal.bark();
  }
}
in 演算子によるプロパティ存在での絞り込み。

ガードのロジックが再利用可能になったり、フロー解析には複雑すぎたりする場合は、カスタム型ガード を書きましょう。返り値の型を value is T と注釈した関数です。すると、コンパイラはあなたの実装を信頼します。これは契約です。チェックが不完全なら、型システムが決して気づかない実行時エラーを再び持ち込むことになります。TypeScript 5.5 以降、filter などのコールバックでは単純な述語が自動的に推論されますが、エクスポートされる関数では明示的な注釈が依然として標準です。

TYPESCRIPT
interface User { id: string; email: string }

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    typeof value.id === "string" &&
    "email" in value &&
    typeof value.email === "string"
  );
}

const raw: unknown =
  JSON.parse('{"id":"1","email":"a@b.c"}');
if (isUser(raw)) {
  console.log(raw.email.toLowerCase());
}
使用前に外部データを検証する述語。

理解度チェック

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

  1. なぜ typeof value === "object" のチェックだけでは、非 null のオブジェクトへの絞り込みには不十分なのですか?
    • typeof が大文字の "Object" を返すから
    • JavaScript では typeof null も "object" だから
    • typeof はユニオンでは動作しないから
    • 配列は "array" を返すから
  2. どの返り値の注釈が関数をカスタム型ガードにしますか?
    • boolean
    • value as User
    • value is User
    • asserts value
  3. 不適切に書かれたカスタム型ガードの主なリスクは何ですか?
    • 関数内でのコンパイル時エラー
    • 嘘の絞り込み。実行時には誤っている型をコンパイラが信じてしまう
    • コンパイル性能の低下