【実務・中級編】 infer キーワード – TypeScript実践ガイド

お疲れ。最近、君が書いた型定義のプルリクを見たんだが……おや、また`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 = T extends (…args: any[]) => infer R ? R : never;

// テスト用の関数
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` や `T[]` から型を引っこ抜くテクニックだ。

// 配列の要素の型を推論するカスタム型
type UnpackArray = T extends (infer U)[] ? U : T;

// 文字列の配列
type StringArray = string[];
type ExtractedString = UnpackArray; // string

// 読み取り専用配列(readonly)やタプルにも対応させたい場合の発展形
type DeepUnpack = T extends readonly (infer U)[] ? U : T;

type MyTuple = [string, number, boolean];
// ※通常の (infer U)[] だとタプル全体がマッチしてしまうので、
// 必要に応じて条件をチューニングするのがプロの技だ。

パターンC:Promiseの「中身(Resolved value)」を暴く(現代フロントエンドの必須技)

Async/Awaitが当たり前の現代、Promiseに包まれた型から「中身のデータ型」を取り出したい場面は星の数ほどある。TypeScript標準にも `Awaited` があるが、内部の仕組みはまさに `infer` だ。

// 自作の Promise Unwrapper
type UnwrapPromise = T extends Promise ? UnwrapPromise : T;

// ネストしたPromiseを想定
type AsyncData = Promise>;

// 最終的にstringを取り出す
type ResolvedData = UnwrapPromise; // string

—

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 = T extends (…args: infer P) => any ? P : never;

// 引数が複数ある場合、タプルとして綺麗に推論される
type Fn = (id: string, count: number) => void;
type Params = MyParameters; // [id: string, count: number]

このあたりの挙動が怪しいと感じたら、無理に複雑な `infer` を自作せず、TypeScript標準のユーティリティ型(`Parameters`, `ReturnType` など)で代替できないかまず疑うこと。車輪の再発明はバグの母だ。

—

4. まとめ:型は「仕様書」である

`infer` キーワードをはじめとするアドバンスドな型定義は、単なる「コンパイラを黙らせるための呪文」ではない。
「このコードはこういう構造でデータをやり取りするんだ」という、動的な振る舞いを静的に担保する最強のドキュメント(仕様書)なんだ。

君が書くその型定義が、次にコードを触るメンバー(あるいは未来の自分)の強力なセーフティネットになる。
最初は難しく感じるかもしれないが、まずは既存のユーティリティ型のソースコードを覗いてみるところから始めてみるといい。VSCodeで `ReturnType` に `F12` キー(定義へ移動)を押せば、今回の知識がそのまま活きているのが分るはずだ。

さあ、手を動かして、その型パズルを自分のものにしてくれ。応援しているよ!

コメント

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