おう、元気か? 今日のテーマはTypeScriptの `infer` キーワードだ。これ、Conditional Typesと組み合わせると、もうコードの書き方が一段階も二段階も変わる、まさに「魔法」みたいなもんだ。
「いやいや、そんな大げさな」って思ったか? まあ、落ち着け。まずはこの `infer` ってやつが何者なのか、そしてどういう場面で「なるほど!」ってなるのか、実務でよくあるケースを具体的に見ていこうぜ。
1. `infer` キーワードって、一体何者なんだ?
簡単に言うと、`infer` は「型推論の推論」だ。Conditional Typesってのは、「もしAという型だったらB、そうでなければC」みたいな条件分岐を書くためのものだろ? で、その「Aという型だったら」の部分で、さらに「その型の中身を一旦保留しておいて、後で使う」っていうのが `infer` の役割なんだ。
例えば、こんな感じだ。
// Tは、何かしらの型が入る変数だと思ってくれ
type MyReturnType
この `MyReturnType` っていう型を分解してみよう。
- `T extends (…args: any[]) => infer R`
- 「もし `T` が関数型だったら…」
- 「その関数の戻り値の型を `R` という名前で一旦保留しておく(推論する)」
- `? R`
- 「もし上記が真なら、保留しておいた `R` (戻り値の型) を返す」
- `: never`
- 「もし上記が偽(つまり関数型でなかったら)、`never` 型を返す」
どうだ? `infer R` の部分で、関数 `T` の戻り値の型を「推論」して、それを `R` という変数に格納してる。そして、その `R` をそのまま結果として返してるんだ。
これは、TypeScriptの標準ライブラリでもガンガン使われてるテクニックだ。例えば、Promiseの内部の型を抽出したり、配列の要素の型を取り出したり。これを知ってると、自分でユーティリティ型をゴリゴリ書くときに、ものすごい武器になる。
2. 実務で「これ、`infer` じゃないと無理だろ!」ってなるケース
じゃあ、具体的にどんな場面で `infer` が活躍するのか、いくつか例を見てみよう。
2.1. 関数の戻り値の型を抽出したい
これが一番よく見るパターンかな。ある関数型があって、その「戻り値の型だけ」が欲しい時。
例えば、こんな関数があるとしよう。
function getUserName(id: number): string {
// … 実際にはAPIを叩いたりする処理 …
return `User_${id}`;
}
function getUserInfo(id: number): { name: string; age: number } {
// …
return { name: `User_${id}`, age: 30 };
}
ここで、`getUserName` の戻り値の型(`string`)だけが欲しい。`getUserInfo` の戻り値の型(`{ name: string; age: number }`)だけが欲しい。
`infer` を使わないと、いちいち `string` とか `{ name: string; age: number }` と手で書かないといけない。でも、もし関数のシグネチャが変わったら? そのたびに型定義も修正しないといけないのは面倒だ。
そこで、`infer` の出番だ。
// infer を使って関数の戻り値の型を抽出するユーティリティ型
type GetReturnType
// 関数型を直接渡して、戻り値の型だけを取り出す
type UserNameType = GetReturnType
type UserInfoType = GetReturnType
// 型を確認してみよう
const userName: UserNameType = “Alice”;
const userInfo: UserInfoType = { name: “Bob”, age: 25 };
console.log(userName, userInfo);
`typeof getUserName` ってやって、型レベルで関数そのものを参照してるのがポイントだ。そして、`T extends (…args: any[]) => infer R` の部分で、もし `T` が関数だったら、その戻り値の型を `R` として推論してくれる。
2.2. 配列の要素の型を抽出したい
これもよくある。「ある配列型があって、その配列の要素の型だけ」が欲しい時。
例えば、こんな型定義があったとする。
type UserList = { id: number; name: string }[];
type ProductList = string[];
この `UserList` から `{ id: number; name: string }` を、`ProductList` から `string` を取り出したい。
// infer を使って配列の要素の型を抽出するユーティリティ型
type GetArrayElementType
// 型を抽出してみる
type UserElementType = GetArrayElementType
type ProductElementType = GetArrayElementType
// 型を確認してみよう
const userElement: UserElementType = { id: 1, name: “Charlie” };
const productElement: ProductElementType = “Apple”;
console.log(userElement, productElement);
`T extends (infer R)[]` の部分が肝だ。これは「もし `T` が `R` 型の要素をいくつも持つ配列だったら」という意味になる。`infer R` で配列の要素の型 `R` を推論し、それを返している。
2.3. Promise の中身の型を抽出したい
非同期処理でよく使う `Promise` 型。`Promise
// infer を使って Promise の中身の型を抽出するユーティリティ型
type GetPromiseValue
// 例: 非同期処理の戻り値の型
async function fetchUserData(): Promise<{ userId: number; userName: string }> {
// … API 呼び出し …
return { userId: 123, userName: “David” };
}
// fetchUserData の戻り値の型は Promise<{ userId: number; userName: string }>
// ここから中身の型だけを取り出す
type UserDataType = GetPromiseValue
// UserDataType は { userId: number; userName: string } になる
// 型を確認してみよう
const userData: UserDataType = { userId: 456, userName: “Eve” };
console.log(userData);
`T extends Promise
`ReturnType
3. `infer` を使う上での注意点とベストプラクティス
`infer` は強力だけど、いくつか気をつけておきたいこともある。
3.1. `infer` は Conditional Types の中でしか使えない
これは仕様だから仕方ない。`infer` は「条件付きの型推論」だから、その条件分岐がないと意味がないんだ。
3.2. `infer` で推論できる型は一つだけ
一つの `infer` キーワードで推論できる型は、その部分で表される型だけだ。複数の型を同時に推論したい場合は、複数回 `infer` を使うか、タプル型などを活用してまとめて推論する必要がある。
例えば、関数の引数と戻り値の両方を推論したい場合。
// infer を使って引数と戻り値の型を同時に取得しようとする例(少し複雑)
type GetArgsAndReturn
function exampleFn(a: number, b: string): boolean {
return true;
}
type FnInfo = GetArgsAndReturn
// FnInfo は { args: [a: number, b: string]; return: boolean; } になる
const args: FnInfo[‘args’] = [10, “hello”];
const ret: FnInfo[‘return’] = false;
console.log(args, ret);
ここでは、`infer Args` で引数の型(タプル型になる)を推論し、`infer Return` で戻り値の型を推論している。
3.3. 型の「名前」ではなく「構造」で判断する
`infer` は、渡されてきた型が「どういう構造になっているか」に基づいて推論する。だから、`string` 型を `string` と推論するわけじゃなくて、「文字列リテラルの型」とか「文字列型の配列」みたいな、その型が持つ構造を捉えてくれる。
3.4. 複雑すぎる型推論は可読性を下げる可能性も
`infer` を駆使すると、ものすごく複雑な型を動的に定義できる。それはそれで素晴らしいんだけど、あまりにも複雑になりすぎると、他の開発者が読みにくくなる可能性がある。
チームで開発しているときは、その「複雑さ」と「得られるメリット」のバランスをよく考えることが大事だ。場合によっては、素直に型を定義した方が分かりやすいこともある。
4. まとめ: `infer` は TypeScript の「関節」を柔らかくする
今日の話、どうだった? `infer` キーワードは、Conditional Typesと組み合わせることで、TypeScriptの型システムに「柔軟性」と「動的な推論能力」をもたらしてくれる、まさに縁の下の力持ちだ。
- 関数の戻り値型
- 配列の要素型
- Promise の中身の型
これらを、元の型定義に依存せずに動的に抽出できるようになる。これにより、コードの再利用性が高まり、型定義のメンテナンスコストも下げられる。
最初はちょっと難しく感じるかもしれないけど、一度この「型推論の推論」という考え方に慣れてしまえば、TypeScriptのコードがもっと面白く、もっとパワフルに書けるようになるはずだ。
ぜひ、今日紹介したサンプルコードを自分のプロジェクトで試してみてくれ。きっと「なるほど!」って思える瞬間が訪れるはずだから。
じゃあ、またな!

コメント