【入門編】Interfaceのジェネリクス制約における「infer」の活用:関数の戻り値から型を抽出する – TypeScript コア・型システムの基礎解析バイブル

こんにちは。TypeScriptの世界へようこそ。

現場でバリバリと開発をしていると、「APIから返ってくるこの複雑なレスポンス、わざわざ型定義を書き直すの面倒だな…」と思う瞬間、ありますよね。

今回は、TypeScriptの型システムの中でも特に強力な武器である「`infer` キーワード」を使って、「関数の戻り値から型を自動抽出する」という、実務で死ぬほど役に立つテクニックを伝授します。これさえマスターすれば、型定義の重複地獄から解放されますよ。

—

1. なぜ「戻り値の抽出」が必要なのか?

例えば、バックエンドから複雑なデータ構造が返ってくるAPIがあるとします。

// バックエンドが提供するAPI関数(実際にはもっと複雑なはずです)
async function fetchUserProfile() {
return {
id: 1,
name: “Alice”,
settings: { theme: “dark”, notifications: true },
roles: [“admin”, “editor”]
};
}

もし、この関数の戻り値を使って別の関数を作りたいとき、わざわざ `interface UserProfile { … }` と手書きしていませんか?

もしAPI側の型が変わったら、手書きのインターフェースを全部修正しなければなりません。これはバグの温床です。「関数の実行結果から型を勝手に抜き出してくれればいいのに」――それを実現するのが `infer` です。

—

2. 「infer」の正体:型の中の「変数」

`infer` は、「型の中で使う一時的な変数」だと考えてください。

「この関数の戻り値が `T` 型だとしたら、その `T` を抽出してくれ!」という命令を、条件分岐(Conditional Types)の中で記述します。

基本的な抽出ツール:ReturnType

TypeScriptには、標準で `ReturnType` という便利なユーティリティ型が用意されています。まずはこれの中身を見てみましょう。

type MyReturnType = T extends (…args: any[]) => infer R ? R : never;

このコードの読み解き方はこうです:
1. `T extends (…args: any[]) => …` : 「Tは関数型ですか?」と聞いています。
2. `… => infer R` : 「もし関数なら、その戻り値を `R` という名前の変数に入れて!」と指示しています。
3. `? R : never` : 「関数だったら抽出した `R` を返し、そうでなければ `never`(型なし)にするよ」という意味です。

これが、TypeScriptが裏側で型を推論している正体です。

—

3. 実践:複雑なAPIレスポンスから型を抽出する

では、実際に現場で使うパターンを見てみましょう。

// 1. 先ほどのAPI関数
const fetchUserProfile = async () => ({
id: 1,
name: “Alice”,
settings: { theme: “dark” as const },
});

// 2. inferを使って、関数の戻り値型だけを抽出
type UserProfile = ReturnType;

// 3. 抽出した型をインターフェースのように扱う
function printUserName(user: UserProfile) {
console.log(user.name); // 型安全!
}

ここで重要なのは `typeof` です。`fetchUserProfile` は値(関数そのもの)ですが、`typeof` をつけることで型情報に変換し、それを `ReturnType` に渡しているのです。

—

4. 陥りやすい罠と解決策

初学者が必ず一度はハマるポイントがいくつかあります。ここをクリアすれば、もう中級者です。

罠①:Promiseの解決に失敗する

`fetchUserProfile` のような `async` 関数は、常に `Promise` を返します。そのため、単純に `ReturnType` を使うと `Promise<{...}>` という型が取れてしまいます。

解決策:
`Awaited` という便利なユーティリティ型を組み合わせます。

type RawResult = ReturnType; // Promise<{...}>
type User = Awaited; // { id: number, … }

これで、非同期処理のラップを剥がした「真の戻り値型」が手に入ります。

罠②:関数の引数で「infer」を使おうとして混乱する

`infer` は「抽出」する場所によって役割が変わります。戻り値だけでなく、関数の引数も抽出可能です。

type FirstArg = T extends (arg: infer A, …args: any[]) => any ? A : never;

// 使用例
type MyFunc = (id: number, name: string) => void;
type Arg0 = FirstArg; // number型が抽出される!

—

5. まとめ:TypeScriptの極意

今回紹介した `infer` や `ReturnType` は、「型を手書きするな、コードの構造から自動生成せよ」というTypeScriptの哲学を体現するものです。

  • `infer` は型の中の変数である
  • `typeof` で値を型に昇格させる
  • `ReturnType` や `Awaited` で複雑な型を「逆算」する

最初は魔法のように見えるかもしれませんが、コンパイラは常に「この関数の戻り値は何か?」を解析しています。私たちはその解析結果を、`infer` を使って少しだけお裾分けしてもらっているに過ぎないのです。

ここをマスターすれば、もう「型定義の修正に追われる日々」とはおさらばです。ぜひ、今日書いているコードで `ReturnType` を試してみてください。その瞬間、TypeScriptがあなたにとって単なる縛りではなく、強力なパートナーに変わるはずですよ。

それでは、また次の深淵でお会いしましょう。ハッピーコーディング!

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