型の絞り込み

TypeScript の型ガードや制御フロー分析で、ユニオン型をより具体的な型に絞り込む手法

TypeScript型システム

型の絞り込みとは

型の絞り込み (Type Narrowing) は、TypeScript の制御フロー分析により、ユニオン型 (string | number | null) をより具体的な型 (string) に絞り込む仕組みである。if 文や typeof チェックの後、TypeScript コンパイラが自動的に型を推論する。

function process(value: string | number | null) {
  // ここでは value は string | number | null
  if (value === null) return;
  // ここでは value は string | number (null が除外された)
  if (typeof value === 'string') {
    // ここでは value は string
    console.log(value.toUpperCase());
  } else {
    // ここでは value は number
    console.log(value.toFixed(2));
  }
}

絞り込みの方法

typeof ガード

function format(value: string | number): string {
  if (typeof value === 'string') return value.trim();
  return value.toFixed(2); // number に絞り込まれる
}

instanceof ガード

function handleError(err: Error | string) {
  if (err instanceof Error) {
    console.error(err.message, err.stack);
  } else {
    console.error(err);
  }
}

in 演算子

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ('swim' in animal) animal.swim(); // Fish に絞り込み
  else animal.fly();                    // Bird に絞り込み
}

Discriminated Union (判別共用体)

最も強力な絞り込みパターン。共通の type フィールドで型を判別する。

type ApiResponse =
  | { type: 'success'; data: User }
  | { type: 'error'; message: string }
  | { type: 'loading' };

function render(response: ApiResponse) {
  switch (response.type) {
    case 'success': return <UserProfile user={response.data} />;
    case 'error':   return <ErrorMessage message={response.message} />;
    case 'loading': return <Spinner />;
  }
}

カスタム型ガード

function isUser(value: unknown): value is User {
  return typeof value === 'object' && value !== null && 'id' in value && 'name' in value;
}

const data: unknown = JSON.parse(body);
if (isUser(data)) {
  console.log(data.name); // User 型に絞り込まれる
}

網羅性チェック (Exhaustive Check)

never 型を使って、switch 文で全ケースを処理していることをコンパイル時に保証する。

function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`);
}

function handleStatus(status: 'active' | 'inactive' | 'suspended') {
  switch (status) {
    case 'active': return 'アクティブ';
    case 'inactive': return '非アクティブ';
    case 'suspended': return '停止中';
    default: return assertNever(status); // 新しいステータスを追加し忘れるとコンパイルエラー
  }
}

絞り込みが効かないケース

// ❌ コールバック内では絞り込みが効かない
function process(value: string | null) {
  if (value !== null) {
    setTimeout(() => {
      value.toUpperCase(); // エラー: value は string | null のまま
    }, 100);
  }
}

// ✅ ローカル変数に代入して解決
function process(value: string | null) {
  if (value !== null) {
    const v = value; // v は string に絞り込まれる
    setTimeout(() => { v.toUpperCase(); }, 100);
  }
}

型ガードの種類

型ガード 構文 用途
typeof typeof x === 'string' プリミティブ型
instanceof x instanceof Error クラス
in 'name' in x プロパティの存在
Discriminated Union x.kind === 'circle' ユニオン型
カスタム型ガード function isUser(x): x is User 複雑な判定

型の絞り込みについては関連書籍でも詳しく扱われている。

関連用語