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

こんにちは!TypeScriptを触り始めると、「あ、これって入力する値によって、返ってくる型をガラリと変えられたら最高なのに……!」と思う瞬間が必ずやってきますよね。他の言語から来た方ならなおさら、「if文みたいに型を分岐させたい!」と感じるはずです。

実は、TypeScriptの型推論エンジンには、まさにその願いを叶える「条件付き型(Conditional Types)」という強力な秘密兵器が備わっています。

ここをクリアすれば、あなたももうTypeScriptの初級者を卒業し、型システムを自在に操るエンジニアの仲間入りです。今日は、この条件付き型の基礎から、現場で即戦力になる応用テクニックまで、優しく紐解いていきましょう!

—

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

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

JavaScriptや他のプログラミング言語で、`条件 ? Trueの場合の値 : Falseの場合の値` という書き方を見たことがありますよね。あれの「値」の部分が、すべて「型」に置き換わったものだと考えてください。

基本的な文法は、たったこれだけです。

T extends U ? X : Y

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

この `extends` というキーワード、「継承」という意味で覚えている人が多いのですが、型推論の世界では「包含関係(より大きな型の中に収まるか)」をチェックするフィルターだと捉えると、一気に理解がクリアになりますよ。

—

2. まずは基本:入力に応じて型を切り替えてみよう

それでは、実際にコードを書いてその動きを見てみましょう。
ここでは、「渡された型が文字列(string)なら配列にして返し、それ以外ならそのまま返す」という型を作ってみます。

// Tが string なら T[] に、そうでなければ T そのままになる型
type ToArray = T extends string ? T[] : T;

// 使い方と推論結果のイメージ
type Result1 = ToArray; // string[] (文字列なので配列に変化!)
type Result2 = ToArray; // number (数値なのでそのまま)

すごいですよね!コンパイラが `T` に何が入ってきたかを静的に判定し、返す型をその場で動的に書き換えています。

ここで、頭の中でイメージしやすいように、コンパイラの内部で行われている型評価の流れを図解的に見てみましょう。

[入力された型: T]
│
▼
┌───────────┐ YES ┌──────────────┐
│ T extends │───────────► │ 型は T[] に! │
│ string? │ └──────────────┘
└───────────┘
│ NO ┌──────────────┐
└──────────────────►│ 型は T のまま│
└──────────────┘

これが条件付き型の基本の仕組みです。シンプルですが、ここにあらゆる型パターンのロジックを組み込むことができるのです。

—

3. 実践!関数と組み合わせて「戻り値の型」を動的に変える

型単体の定義ができるようになったら、次は関数と組み合わせてみましょう。ここからが実務で一番使うアプローチです。

例えば、「引数が文字列なら数値(文字数)を返し、引数が数値なら文字列を返す」という、ちょっとひねった関数を作りたいとします。これを素朴に書こうとすると `any` や型アサーション(`as`)に頼りがちになりますが、条件付き型を使えば、型安全を100%保ったまま実現できます。

// 1. 入力型に応じて出力型を決定する条件付き型を定義
type ProcessResult = T extends string ? number : string;

// 2. オーバーロードと条件付き型を組み合わせた関数シグネチャ
function processValue(value: T): ProcessResult {
if (typeof value === “string”) {
// 実行時には string を受け取っているので、返り値は必ず number
return value.length as ProcessResult;
} else {
// 実行時には number を受け取っているので、返り値は必ず string
return value.toString() as ProcessResult;
}
}

// — 実際の呼び出しと型推論の魔法 —
const res1 = processValue(“Hello, TypeScript”); // res1 の型は ‘number’
const res2 = processValue(42); // res2 の型は ‘string’

console.log(res1); // 17 (数値)
console.log(res2); // “42” (文字列)

このコードの美しいところは、呼び出し側が渡した引数の型をコンパイラが完璧に追跡し、戻り値の型を自動で確定させてくれる点です。`res1` にホバーすれば `number` と表示され、`res2` には `string` と表示されます。バグの入り込む隙がありませんね。

—

4. 陥りやすい文法エラーと罠

さて、条件付き型を使い始めると、誰もが一度はハマる「罠」があります。ここでは代表的なものを2つ紹介しておきますね。

罠その1:ユニオン型(直和型)を渡したときの「分配法則」

例えば、先ほどの `ToArray` に `string | number` という「文字列か数値のどちらか」というユニオン型を渡すとどうなるでしょうか?

type Result3 = ToArray;
// 予想: (string | number)[] ?
// 実際: string[] | number[] (えっ、別々に分配された!?)

これはTypeScriptの「分散条件付き型(Distributive Conditional Types)」という仕様によるものです。
型パラメータが裸(unwrapped)の状態で条件付き型に渡されると、コンパイラは自動的にユニオン型の要素を1つずつ分解して、それぞれに条件を適用し、最後に再度ユニオンで結合するという親切(余計なお世話?笑)な挙動をします。

もし、分配させずに「ユニオン型全体を一つの塊として判定したい」場合は、以下のように `[]` で型パラメータを囲んで裸のままにしないようにします。

type ToArrayNonDistributive = [T] extends [string] ? T[] : T;

type Result4 = ToArrayNonDistributive;
// 結果: string | number (全体として string を拡張していないので、そのまま返る)

この「分配する・しない」の挙動は、高度なライブラリやユーティリティ型を作る際によく躓くポイントなので、ぜひ覚えておいてください。

—

5. まとめ

いかがでしたか?今回は、TypeScriptの型推論エンジンをハックする「条件付き型」の基礎と応用について解説しました。

  • 条件付き型 (`T extends U ? X : Y`) は、型の世界における三項演算子である。
  • 関数やジェネリクスと組み合わせることで、入力に応じた動的で完璧な型安全性を手に入れられる。
  • ユニオン型を渡したときの「分配法則」には少しだけ注意が必要。

ここをクリアできれば、TypeScriptの型システムはあなたにとって「縛りプレイの道具」から「最高に頼れる相棒」へと変わります。ぜひ今日のコードを手元のエディタ(VSCodeなど)に貼り付けて、色々型を変えて遊んでみてくださいね。

あなたのTypeScriptライフが、より一層エキサイティングなものになりますように!

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