こんにちは!TypeScriptの型システムの世界へようこそ。
日々フロントエンドからバックエンドまでコードを書いていると、「あ、この入力値のときは、戻り値の型を自動でこっちに変えたいな」と思う瞬間ってありますよね。
他のプログラミング言語、例えばJavaやC#、あるいはJavaScriptの動的な世界からやってくると、「条件によって型を切り替える」という発想は少し難しく感じるかもしれません。でも大丈夫。ここをクリアすれば、TypeScriptの型システムの本質がグッと見えてきて、コードを書くのがもっと楽しくなりますよ。
今回は、TypeScriptの真骨頂であるConditional Types(条件付き型)を使って、入力された型に応じて戻り値の型をダイナミックに変更する、高度なジェネリック関数の型定義手法を一緒にマスターしていきましょう!
—
1. 条件付き型(Conditional Types)ってなに?
一言でいうと、条件付き型とは「型のための三項演算子」です。
JavaScriptで `condition ? trueVal : falseVal` という書き方をしますよね。あれの「型バージョン」だと思ってください。
基本的な文法はこうなります:
T extends U ? X : Y
日本語に翻訳すると、こうです。
「もし型 `T` が型 `U` に代入可能(extends)なら、結果は型 `X`。そうでなければ型 `Y` になるよ」
これを関数の世界に応用すると、「引数に渡された型をパトロールして、型ごとに異なる戻り値をピシッと割り当てる」という魔法のようなジェネリック関数が作れるようになります。
—
2. 具体例で体感しよう:入力によって戻り値をガラリと変える
イメージしやすいように、ちょっとした実用的な例を考えてみましょう。
「入力された値が `string` だったら文字列を返し、`number` だったら数値を返す。そんなお行儀のいい関数」を作りたいとします。これを、Conditional Typesを使って型定義してみますね。
// ① 渡された型 T に応じて戻り値の型を動的に決定するヘルパー型
type CheckType
// ② 上記の条件付き型を利用するジェネリック関数
function processValue
// 実行時の処理(簡易的な例です)
if (typeof value === “string”) {
// ここで as CheckType
return `Processed string: ${value}` as CheckType
} else {
return (value 2) as CheckType
}
}
// === 実際に使ってみましょう ===
// res1 の型は 「string」 に自動で決定されます!
const res1 = processValue(“Hello TypeScript”);
console.log(res1); // “Processed string: Hello TypeScript”
// res2 の型は 「number」 に自動で決定されます!
const res2 = processValue(42);
console.log(res2); // 84
ここで注目してほしいのは、`processValue` を呼び出した瞬間に、TypeScriptのコンパイラが「引数の型 `T` が何であるか」を静的に判定し、戻り値の型(`CheckType
`res1` にホバーすれば `string` と表示され、`res2` なら `number` と表示されます。型安全でありながら、非常に柔軟ですよね。
—
3. 脳内トレース:コンパイラはどう型を評価しているのか?
初学者のうちや、他の言語から移行したときによやってしまいがちな「勘違い」を一つシェアしておきますね。
> 「あれ? `if (typeof value === ‘string’)` で実行時に判定してるんだから、型もそこで自動で切り替わってるんじゃないの?」
実は、ここがTypeScriptを学ぶ上で一番面白いポイントです。
実行時の `if` 文は、あくまでJavaScriptのランタイム(実行時)の振る舞いです。
一方で、Conditional Types (`T extends U ? X : Y`) は、私たちがコードを書いている最中、あるいはビルドする前のコンパイル時(型チェック時)に評価されます。
つまり、データの流れをイメージするとこういうことです:
【コード記述・コンパイル時】
引数の型 T ──> [ Conditional Types (T extends string ? …) ] ──> 戻り値の型が確定!
【コード実行時】
実際の値 value ──> [ 実行時の if (typeof …) ] ──> JavaScriptとして処理される
コンパイラはコードを走らせる前に、「この `T` は何だ?」と型レベルで計算を終わらせています。だからこそ、IDE(VS Codeなど)で補完が爆速で効くし、間違った型を渡したら一瞬で赤くエラーを出して守ってくれるんです。
—
4. 陥りがちな文法エラーと、その対策
Conditional Typesを書き始めると、誰もが一度は次のような壁(コンパイルエラー)にぶつかります。
エラー例:関数内部での型アサーションの必要性
先ほどのコードの関数内で、`return … as CheckType
function processValue
if (typeof value === “string”) {
// ここで `as CheckType
return `Processed string: ${value}`;
// ❌ Error: Type ‘string’ is not assignable to type ‘CheckType
}
// …
}
「あれ? `T` が `string` なんだから、返り値も `string` で合ってるじゃん!」と人間は思うのですが、TypeScriptのコンパイラは非常に慎重です。
コンパイラは「ジェネリック型 `T` が将来的にどう変化するか分からないので、内部の戻り値が本当に `CheckType
—
5. さらに先へ:TypeScriptをマスターするための視点
今回紹介した Conditional Types は、単体でも強力ですが、実はTypeScriptの標準ライブラリ(`Exclude`, `Extract`, `ReturnType` など)の裏側でも大量に使われている超重要テクニックです。
ここをクリアできれば、単なる「型を付けるだけのエンジニア」から、「複雑なライブラリの型定義をも読み解き、自分で型安全なAPIを設計できるアーキテクト」への大きな一歩を踏み出したことになります。
最初は少し頭がこんがらがるかもしれませんが、「型レベルのif文なんだな」と割り切って、まずは小さな関数で遊んでみてください。
ここをクリアすれば、TypeScriptの基本はバッチリマスターできますよ!
次回の解説もお楽しみに!