【入門編】TypeScriptの型推論エンジンをハックする:条件付き型の基礎 – TypeScript コア・型システムの基礎解析バイブル

こんにちは!TypeScriptの世界へようこそ。
フロントエンドからバックエンドまで、型安全な開発の心地よさを知ってしまうと、もう他の言語には戻れなくなりますよね。

さて、今回はTypeScriptの型システムにおける「最強の魔法」の一つ、条件付き型(Conditional Types)についてお話しします。

「`T extends U ? X : Y` ってなんだか難しそう…」
そう思っていませんか?大丈夫です。ここをクリアすれば、あなたのTypeScriptスキルは一気に一段階上のステージへと駆け上がりますよ。

今日は、コンパイラの頭の中で何が起きているのかを覗き見しながら、この条件付き型の基礎を優しく、そして本質的にマスターしていきましょう!

—

1. 条件付き型(Conditional Types)ってなに?

一言で言うと、条件付き型とは「型のための三項演算子」です。

JavaScriptや他のプログラミング言語で、`condition ? trueの場合 : falseの場合` という書き方を見たことがありますよね。あれの「型バージョン」だと思ってください。

基本的な構文はこうなります。

T extends U ? X : Y

これを日本語に翻訳すると、こうなります。
「もし型 `T` が型 `U` に代入可能(extends)なら、結果は型 `X`。そうでなければ型 `Y` にしなさい」

これだけ聞くと「ふーん、で?」と思うかもしれませんが、これが型推論エンジンと組み合わさると、入力された型に応じて変幻自在に形を変える「動的な型」を作り出すことができるんです。

—

2. 脳内イメージ:コンパイラは型をどう判定している?

TypeScriptのコンパイラは、コードをビルド(コンパイル)するときに、コードの中の「型」が満たす条件を次々と評価していきます。

イメージとしては、こんな感じです。

[入力された型 T]
↓
「T は U の条件を満たしているか?」
┣━ Yes ──> [型 X を採用!]
┗━ No ──> [型 Y を採用!]

最もシンプルな具体例を見てみましょう。传入れた型が「文字列(string)」かどうかを判定する型を作ってみます。

// IsString 型の定義
type IsString = T extends string ? “Yes, it is a string!” : “No, it is not.”;

// 実際に使ってみる(型を代入して評価)
type Test1 = IsString; // 評価結果: “Yes, it is a string!”
type Test2 = IsString; // 評価結果: “No, it is not.”

コンパイラは、`Test1` を見たときに `string extends string` が「真(True)」だと判定し、結果として `”Yes, it is a string!”` というリテラル型を割り当てます。`number` の場合は「偽(False)」なので、後半の型が選ばれます。

どうですか?仕組み自体はとってもシンプルですよね。

—

3. 実践!関数と組み合わせた「スマートな型変化」

基礎がわかったところで、もう少し実践的な例を見てみましょう。
「受け取った引数の型によって、返り値の型を自動で切り替える関数」を作ってみます。

例えば、「もしID(数値)が渡されたらユーザー名を返し、名前(文字列)が渡されたら詳細オブジェクトを返す」ような関数を考えてみましょう。

// 条件付き型を使って、入力に応じた戻り値を定義する
type ApiResponse = T extends number
? { userId: T; name: string }
: { query: T; results: string[] };

// この型を使って関数をオーバーロード(または型注釈)する
function fetcher(input: T): ApiResponse {
// 実装はダミーです
if (typeof input === “number”) {
return { userId: input, name: “Taro” } as ApiResponse;
} else {
return { query: input, results: [“Apple”, “Banana”] } as ApiResponse;
}
}

// — 使う側の体験 —
const res1 = fetcher(100);
// res1 の型は自動的に { userId: number; name: string; } に推論される!

const res2 = fetcher(“fruit”);
// res2 の型は自動的に { query: string; results: string[]; } に推論される!

お気づきでしょうか? 使う側は複雑な型を意識する必要がなく、ただ値を渡すだけで、TypeScriptの型推論エンジンが完璧に型を追跡してくれます。これが条件付き型の真骨頂です。

—

4. 陥りやすい罠と文法エラー:初心者がハマるポイント

ここで、初学者がよくやってしまう「つまずきポイント」をいくつかご紹介しておきます。知っておくだけで無駄なバグやエラーを防げますよ!

罠①: `extends` の左側は「何でもいいわけではない」

条件付き型の `T extends U` における `T` は、多くの場合ジェネリクス(型引数)として渡されてきます。具体的な型が決まっていない状態(抽象的な型)に対して条件を評価するためです。

もし、最初から具体的な型を書いてしまうと、条件分岐の意味がなくなってしまいます。

// ❌ 意味のない書き方(常に string になってしまう)
type BadExample = string extends number ? true : false;

罠②: 分配条件付き型(Distributive Conditional Types)の挙動に注意

ここが少しアドバンスドですが、超重要です。
ジェネリクス型 `T` にユニオン型(`A | B` のような「or」の型)を渡すと、条件付き型は自動的に「分配(バラバラに処理)」されます。

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

// string | number を渡すとどうなる?
type Result = ToArray;
// 予想: (string | number)[] ?
// 実際: string[] | number[] (それぞれの型にバラされてから配列化される!)

「ユニオン型を渡したら、勝手に一つずつバラして評価される」という挙動(分配法則)があることを、頭の片隅に置いておいてください。これを防ぎたい場合は、次のように `T` を角括弧 `[]` で囲んでタプルにします。

// 分配を阻止するテクニック
type ToArrayStrict = [T] extends [any] ? T[] : never;

type ResultStrict = ToArrayStrict;
// 結果: (string | number)[] (ひとまとまりとして評価される)

—

まとめ:ここをクリアすれば基本はバッチリ!

お疲れ様でした!今回は、TypeScriptの条件付き型(`T extends U ? X : Y`)の基礎について解説しました。

  • 条件付き型とは?:型のための三項演算子であり、入力に応じて動的に型を変化させる仕組み。
  • どう動く?:コンパイラが `extends` の条件を評価し、真か偽かに応じて適切な型を採用する。
  • 注意点:ユニオン型を渡したときの「分配法則」に気をつける。

条件付き型は、TypeScriptが提供する標準ライブラリ(`Omit` や `Extract`、`NonNullable` など)の内部でも大量に使われている、いわば「モダンTypeScriptの共通言語」です。

ここをマスターしたあなたなら、既存の型ユーティリティの仕組みもすんなり理解できるはず。ぜひご自身のコードでも試して、型を自在に操る楽しさを体感してみてくださいね!

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