Featured image of post 型システムにおける条件分岐:条件付き型(Conditional Types)の基礎 Featured image of post 型システムにおける条件分岐:条件付き型(Conditional Types)の基礎

型システムにおける条件分岐:条件付き型(Conditional Types)の基礎

TypeScriptの条件付き型(Conditional Types)の基礎と実践を解説。ジェネリクスを活用したT extends U ? X : Y構文で型レベルの条件分岐を実現し、引数の型に基づいて戻り値を動的に推論させる高度な型定義テクニックを紹介します。

はじめに

TypeScriptの型システムはチューリング完全であり、その力を最大限に引き出す機能が**条件付き型(Conditional Types)**です。JavaScriptの三項演算子と同じように、T extends U ? X : Y という構文で型レベルの条件分岐を実現します。


基本構文

条件付き型は、ある型が別の型を**拡張(extends)**しているかを判定します:

type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>; // true
type B = IsString<42>;      // false

T extends U は「TU に代入可能か?」を意味します。真ならtrueブランチ、偽ならfalseブランチに解決されます。


inferキーワード

infer を使うと、条件の中で型を抽出できます:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function greet() { return 'hello'; }
type G = ReturnType<typeof greet>; // string

infer R は型変数 R を宣言し、マッチした位置から推論します。これは多くのユーティリティ型の基盤です。


分配法則(Distributive Conditional Types)

条件付き型をユニオン型に適用すると、各メンバーに分配されます:

type ToArray<T> = T extends any ? T[] : never;

type Result = ToArray<string | number>;
// string[] | number[]  ((string | number)[] ではない)

分配を防ぐには、条件の両辺をタプルでラップします:

type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;

type Result2 = ToArrayNonDist<string | number>;
// (string | number)[]

組み込みユーティリティ型

標準ライブラリの多くの型が条件付き型を使っています:

// Exclude — ユニオンから型を除去
type T0 = Exclude<'a' | 'b' | 'c', 'a'>;
// 'b' | 'c'

// Extract — 条件に合う型のみ抽出
type T1 = Extract<string | number | (() => void), Function>;
// () => void

// NonNullable — null と undefined を除去
type T2 = NonNullable<string | number | null | undefined>;
// string | number

Exclude の実装はシンプルです:

type MyExclude<T, U> = T extends U ? never : T;

実践的なパターン

入力に応じた戻り値の型

type AsyncResult<T> = T extends Promise<infer U> ? U : T;

async function fetchData(): Promise<string> {
  return 'data';
}

type Data = AsyncResult<ReturnType<typeof fetchData>>; // string

オーバーロードを排除

type StringOrNumber<T> = T extends string ? string : number;

function process<T extends string | number>(val: T): StringOrNumber<T>;
function process(val: any): any {
  return typeof val === 'string' ? val.toUpperCase() : val * 10;
}

const a = process('hello'); // string
const b = process(42);      // number

再帰的な条件付き型

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};

まとめ

条件付き型は型システムにおけるif文です。infer や分配法則と組み合わせることで、高度な型変換を実現し、コンパイル時にバグを検出できます。条件付き型をマスターすれば、真に型安全なTypeScriptコードが書けるようになります。