TypeScriptの型ガードを深く理解する!安全で読みやすいコードへの最短ルート

型ガード typescript TypeScript
この記事は約6分で読めます。

この記事の最終更新日: 2025年5月4日

型ガード typescript

TypeScriptを利用する際に避けて通れないのが、“ある値が本当に期待どおりの型であるか”を実行時に確認する必要性です。コードが複雑になるほど、想定しない型の値が混入するリスクは高まり、思わぬバグの温床になりかねません。そこで登場するのが型ガード(Type Guard)です。本記事では丁寧な解説を通じて、標準的な型ガードから拡張テクニックまで網羅的に紹介します。


型ガードとは何か?

型ガードとは、実行時に与えられた値の型を判別し、TypeScriptコンパイラに“ここではこの型です”と教えてあげる仕組みです。これにより、ifブロックやswitch文の内部で型が絞り込まれ(ナローイング)、自動補完や型チェックが正確かつ安全に行われます。

なぜ必要なのか?

  • 動的データ:APIレスポンスや外部ライブラリから返ってくる値は型保証が弱いため、安全に扱うには実行時チェックが欠かせません。
  • 可読性向上:明示的に型を判別することで、コードの意図を読み手に伝えやすくなります。
  • バグ防止:型エラーの早期発見と位置特定が容易になるため、リファクタリングにも強くなります。

標準で用意されている型ガード

TypeScriptでは、よく使う判定があらかじめ組み込まれています。代表的なものを5つ見ていきましょう。

型ガード手法概要補足
typeofJavaScriptの組み込み演算子を利用typeof x === "string"stringnumberbooleanなど原始型を判定
instanceofオブジェクトが特定のクラスかどうかdateObj instanceof Dateクラスコンストラクタを跨いだ生成元ではfalseになる場合あり
inオブジェクトに指定プロパティが存在するか'id' in userプロパティの有無によるオブジェクト型判定に便利
Array.isArray値が配列であるかを判定Array.isArray(items)ジェネリクスとも相性が良い
真偽値チェックnull/undefinedの排除if (value) { … }0や空文字列も偽と判定されるため注意
function process(input: string | string[] | null) {
  // nullやundefinedを排除
  if (!input) return;

  // 配列かどうか
  if (Array.isArray(input)) {
    console.log('配列の要素数:', input.length);
  } else {
    console.log('文字列の長さ:', input.length);
  }
}


ユーザー定義型ガードで柔軟に拡張する

時には、標準型ガードだけでは対応できない複雑な型判定が必要です。そのときはユーザー定義型ガードを自作しましょう。

interface Fish { swim(): void }
interface Bird { fly(): void }

// petがFishかどうか判定する関数
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim();  // Fishとして扱われる
  } else {
    pet.fly();   // Birdとして扱われる
  }
}

  • 戻り値型に必ず parameterName is Type を指定します。
  • 関数内部では実際にその型であることを確認するロジックを記述します。

4. アサーション関数で厳密に制御する

asserts を使ったアサーション関数は、誤った型なら例外を投げて処理を止め、その後のコードでは安心して型を扱えるようにするテクニックです。

function assertString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new TypeError('文字列ではありません');
  }
}

const data: unknown = fetchSomeData();
assertString(data);
// ここ以降、dataはstringとして扱える
console.log(data.trim());

  • この関数が例外を投げなかった=検証を通過した、という前提で型が保証されます。
  • 入力バリデーションと型保証を同時に行えます。

判別可能ユニオン(Discriminated Union)×switch文

いわゆる”タグ付きユニオン”を使うことで、switch文内部の各分岐自体が型ガードになります。

type Shape =
  | { kind: 'circle'; r: number }
  | { kind: 'rectangle'; w: number; h: number }
  | { kind: 'square'; size: number };

function calcArea(s: Shape): number {
  switch (s.kind) {
    case 'circle':
      return Math.PI * s.r ** 2;
    case 'rectangle':
      return s.w * s.h;
    case 'square':
      return s.size * s.size;
    default:
      // exhaustiveチェック
      const _exhaustive: never = s;
      return _exhaustive;
  }
}

  • kindプロパティがリテラル型として扱われ、各caseで型が自動的に絞り込まれる。
  • never型を利用して、未対応のケースをコンパイルエラーにできます。

注意すべきポイント

  1. 戻り値にisを忘れない:ユーザー定義型ガードはpet is Fishなどの記述が必須です。
  2. instanceofの限界:異なる実行コンテキスト(iframeなど)では期待どおり動作しない場合があります。
  3. typeof x === 'object'はnullも含む:必ず先にx !== nullをチェックしましょう。
  4. 乱用しないasキャストで誤魔化すのではなく、適切な型ガード関数を整備するのが保守性向上の鍵です。

実践的ベストプラクティス

  • よく使う判定は、即座にユーザー定義型ガードとして切り出す。
  • APIレスポンスなど不定形データには、アサーション関数で厳密に制御する。
  • 複数プロパティで判定したくなったら、判別可能ユニオンの導入を検討。
  • neverチェックで将来的な型追加漏れを防ぐ。

まとめ

型ガードを適切に活用すれば、TypeScriptの持つ型安全性を最大限に引き出しながら、可読性や保守性も高まります。ぜひ今日から標準型ガード、ユーザー定義型ガード、アサーション関数、判別可能ユニオンを組み合わせて、安全なコードを書く習慣を身につけましょう。

コメント

タイトルとURLをコピーしました