お疲れ。最近、君が書いた型定義のプルリクを見たんだが……おや、また`any`で逃げようとした跡があるね(笑)。まあ、焦るな。中級の壁にぶつかっている証拠だ。ここを抜ければ、君はもう一段上の「型を自由自在に操るエンジニア」になれる。
今回は、TypeScriptのConditional Types(条件付き型)の中でも一際異彩を放つ、`infer`キーワードについて徹底的に解説しよう。
公式ドキュメントを読んでも「型を推論する」としか書いてなくてピンとこないかもしれないが、現場のプロ目線で言えば、「複雑な既存の型から、パズルのピースのように特定の型を取り出すための極上のスナイパーライフル」だと思えばいい。
さあ、エンジンを温めてくれ。実務で即座に使えるレベルまで一気に引き上げてやるよ。
—
1. `infer` とは何か?(なぜ現場で必要なのか)
TypeScriptを書いていると、外部ライブラリが提供する型や、複雑にネストされたオブジェクトの「中身の型だけ」が欲しい場面に直面しないか?
例えば、「関数の戻り値の型だけ」や「配列の要素の型だけ」が欲しいとき、わざわざ手動で書き直すのはDRY原則(Don’t Repeat Yourself)に反するし、元が変わったときに破綻する。
ここで登場するのが `infer` キーワードだ。
`infer` は、Conditional Types(`T extends U ? X : Y`)の条件部の中でしか使えないという厳格なルールがある。条件にマッチした際、その一部分を一時的な「型変数」としてキャプチャし、真(true)側の分岐で再利用できるようにする魔法の構文だ。
脳内イメージ:ブラウザやTSコンパイラは裏側で何をしているのか?
TypeScriptの型システムは、最終的にJavaScriptにコンパイルされる時にはすべて消え去る。つまり、ブラウザのJSエンジンは `infer` の存在すら知らない。
では、誰がこれを処理しているのか? TypeScriptの型チェッカー(tsc)だ。
型チェッカーは、コンパイル時にAST(抽象構文木)を走査しながら、ジェネリクスに渡された型がパターンの構造(構造的部分型)に合致するかどうかをパターンマッチングしている。
[渡された型: T] ──MATCH?──> [infer U で部分を切り出す] ──> [U を返す]
この「パターンマッチングによる型の一部分の切り出し」こそが、`infer` の正体なのだ。
—
2. 現場で即効性のある実践コード例
百聞は一見にしかずだ。実務でよく遭遇する3つのユースケースを見ていこう。そのままエディタにコピペして挙動を確認してくれ。
パターンA:関数の「戻り値の型」を強制的に剥ぎ取る(`Awaited` や Redux の Thunk対策など)
APIクライアントから返ってくる関数の戻り値の型を、そのまま別のコンポーネントのPropsとして使いたいときはよくある。しかし、わざわざ別で型を定義するのはダサい。
// 自作の ReturnType を作ってみよう
type MyReturnType
// テスト用の関数
function fetchUserProfile() {
return {
id: 1,
name: ‘Taro Yamada’,
email: ‘taro@example.com’,
};
}
// 関数の型から「戻り値のオブジェクト型」だけを綺麗に抽出する
type UserProfile = MyReturnType
// 展開結果:
// type UserProfile = {
// id: number;
// name: string;
// email: string;
// }
const user: UserProfile = {
id: 2,
name: ‘Hanako Suzuki’,
email: ‘hanako@example.com’,
};
パターンB:配列やタプルから「要素の型」を強奪する
APIから返ってくる配列のレスポンス。その「1要素分の型」が欲しいときに、`Array
// 配列の要素の型を推論するカスタム型
type UnpackArray
// 文字列の配列
type StringArray = string[];
type ExtractedString = UnpackArray
// 読み取り専用配列(readonly)やタプルにも対応させたい場合の発展形
type DeepUnpack
type MyTuple = [string, number, boolean];
// ※通常の (infer U)[] だとタプル全体がマッチしてしまうので、
// 必要に応じて条件をチューニングするのがプロの技だ。
パターンC:Promiseの「中身(Resolved value)」を暴く(現代フロントエンドの必須技)
Async/Awaitが当たり前の現代、Promiseに包まれた型から「中身のデータ型」を取り出したい場面は星の数ほどある。TypeScript標準にも `Awaited
// 自作の Promise Unwrapper
type UnwrapPromise
// ネストしたPromiseを想定
type AsyncData = Promise
// 最終的にstringを取り出す
type ResolvedData = UnwrapPromise
—
3. シニアが教える「陥りがちな罠」とベストプラクティス
`infer` は強力だが、使い所を間違えるとコードベースが「読めない魔術書」になってしまう。チーム開発で嫌われないための注意点を授けよう。
罠1:`any` との混同による型安全性の崩壊
`infer` を使う際、マッチしなかった場合のフォールバック(三項演算子の `false` 側)を適当に `any` にしていませんか?
基本は `never` か元の型 `T` を返すようにしよう。`any` に逃げると、型安全性のチェインが途切れてバグの温床になる。
罠2:TypeScript 4.7以降の「共変(Covariance)」と「反変(Contravariance)」の理解
少しディープな話をしよう。TypeScript 4.7から、同一の型変数に対する複数の `infer`(複数箇所での推論)がサポートされた。
例えば、関数の引数の型(反変の位置)で `infer` を使った場合、交差型(Intersection)ではなくユニオン型(Union)として推論されることがある。
// 関数の引数の型を抽出する例
type MyParameters
// 引数が複数ある場合、タプルとして綺麗に推論される
type Fn = (id: string, count: number) => void;
type Params = MyParameters
このあたりの挙動が怪しいと感じたら、無理に複雑な `infer` を自作せず、TypeScript標準のユーティリティ型(`Parameters
—
4. まとめ:型は「仕様書」である
`infer` キーワードをはじめとするアドバンスドな型定義は、単なる「コンパイラを黙らせるための呪文」ではない。
「このコードはこういう構造でデータをやり取りするんだ」という、動的な振る舞いを静的に担保する最強のドキュメント(仕様書)なんだ。
君が書くその型定義が、次にコードを触るメンバー(あるいは未来の自分)の強力なセーフティネットになる。
最初は難しく感じるかもしれないが、まずは既存のユーティリティ型のソースコードを覗いてみるところから始めてみるといい。VSCodeで `ReturnType` に `F12` キー(定義へ移動)を押せば、今回の知識がそのまま活きているのが分るはずだ。
さあ、手を動かして、その型パズルを自分のものにしてくれ。応援しているよ!

コメント