はじめに
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 は「T は U に代入可能か?」を意味します。真なら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コードが書けるようになります。
