TypeScriptのis演算子(Type Predicates)で作るカスタム型ガード

TypeScript Frontend TypeSystem
結論

val is TargetType でカスタム型ガードを作成し、内部でプロパティの存在・型を厳格検証します。

// TypeScript公式仕様:ユーザー定義型ガード (Type Predicates)
type User = { id: string; name: string };

function isUser(val: unknown): val is User {
  return (
    typeof val === 'object' &&
    val !== null &&
    'id' in val &&
    typeof (val as Record<string, unknown>).id === 'string' &&
    'name' in val &&
    typeof (val as Record<string, unknown>).name === 'string'
  );
}

TypeScript公式仕様:is 演算子と Narrowing の関係

TypeScript(Narrowing)ドキュメントで定義されている Type Predicates(型述語)は、関数の戻り値の型として parameterName is Type という特殊な構文を指定することで、その関数が true を返した if 分岐スコープ内で対象変数を絞り込む(Narrowing)機能です。


実際に起こる事故:偽型ガードによる本番ランタイムクラッシュ

TypeScriptコンパイラは、val is User と宣言された関数の中身(return 条件文)が本当に正しいかどうかを検証しません

以下のような雑な型ガードを書いてしまうと、型システムに大きな穴(Soundness Hole)が空き、重度のバグを引き起こします。

// ❌ 危険:判定が不完全な偽型ガード
function isUserUnsafe(val: any): val is User {
  return typeof val === 'object'; // null の判定漏れ、id/name の型チェック未実施
}

const input: unknown = null;
if (isUserUnsafe(input)) {
  // コンパイラは input を User 型だと信用してしまう!
  console.log(input.name.toUpperCase()); // ❌ 本番で TypeError: Cannot read properties of null クラッシュ
}

堅牢な型ガード作成の手順

  1. 引数の型を any ではなく unknown に指定する
  2. 内部判定で① typeof val === 'object'、② val !== null の両方を必須チェックする
  3. in 演算子または Object.prototype.hasOwnProperty を使用して必要なキーの存在とデータ型をすべて確認する
  4. より複雑なスキーマの場合は、手動 is ガードではなく Zod(schema.safeParse)などのバリデーションライブラリへの移行を検討する