【入門編】関数型における「Parameters」と「ReturnType」ユーティリティ型の活用 – TypeScript コア・型システムの基礎解析バイブル

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

コードを書いていて、「すでにある関数の引数や戻り値と同じ型を、別の場所でも使い回したいな……」と思ったことはありませんか?

たとえば、外部ライブラリが提供している関数の引数型を流用したいのに、型定義が `export` されていなかったり。あるいは、関数をラップしてログを出力する新しい関数を作りたいけれど、元の引数の型をもう一度手書きするのが面倒だったり……。

愚直に同じ型を手動でコピー&ペーストして定義し直すと、元の関数に変更が入った瞬間に型定義の食い違い(同期ズレ)が発生してバグの原因になります。

そこで大活躍するのが、TypeScriptに標準で用意されている組み込みユーティリティ型、`Parameters` と `ReturnType` です。

今回は、この2つの強力なツールを使って「関数そのものから型を自動抽出して再利用する」という、TypeScriptならではのスマートな設計手法を、基礎から本質まで優しく解き明かしていきますね。ここをマスターすれば、TypeScriptの型システムを一段深くコントロールできるようになりますよ!

—

1. 概念を図解で理解しよう:関数を「型」として解剖する

TypeScriptにおいて、関数は「いくつかの引数を受け取り、1つの結果を返す」という構造(シグネチャ)を持っています。

`Parameters` と `ReturnType` は、この構造をコンパイル時にピンセットで分解して取り出すための道具です。

┌─────────────────────────────────────────────────────────┐
│ 元の関数 (Function) │
│ │
│ function fetchUser(id: string, isAdmin?: boolean): Promise
└──────────────┬───────────────────────────────────┬──────┘
│ │
[ 引数リストを抽出 ] [ 戻り値を抽出 ]
▼ ▼
Parameters ReturnType
│ │
▼ ▼
[id: string, isAdmin?: boolean] Promise
(タプル型として取得) (戻り値の型を取得)

  • `Parameters`: 関数の引数の型を、順番通りのタプル型(配列のような固定長の型)として抽出します。
  • `ReturnType`: 関数が返す戻り値の型を抽出します。

—

2. 関数の戻り値型を抽出する「ReturnType」

まずは理解しやすい `ReturnType` から見ていきましょう。

基本的な使い方

たとえば、ユーザー情報を組み立てて返す関数があるとします。

// ユーザーを作成する関数
function createUser(name: string, age: number) {
return {
id: “usr_” + Math.random().toString(36).substring(2, 9),
name: name,
age: age,
createdAt: new Date(),
};
}

// 💡 createUser の戻り値の型を自動抽出!
type User = ReturnType;

// 抽出された User 型は以下の定義と同等になります:
// type User = {
// id: string;
// name: string;
// age: number;
// createdAt: Date;
// };

// 抽出した型を変数や別の関数で再利用できる
const adminUser: User = {
id: “usr_admin001”,
name: “Taro”,
age: 30,
createdAt: new Date(),
};

このように、関数の実装コードを一度書くだけで、戻り値の型定義をTypeScriptが自動で推論し、それを `User` 型として抽出できました。

【超重要】初心者が必ずハマる落とし穴:`typeof` を忘れないこと!

ここで初心者が最もよく遭遇するコンパイルエラーを紹介します。

// ❌ エラーになるコード
type User = ReturnType;
// エラー: ‘createUser’ は値を参照していますが、ここでは型として使用されています。

なぜエラーになるのでしょうか?

TypeScriptには 「値(JavaScriptとして実行時に動く実体)」 の世界と、「型(コンパイル時に型検査のためだけに存在する情報)」 の世界という、明確に分かれた2つの空間があります。

  • `createUser` 単体は、実行時に動く 「値」 です。
  • ユーティリティ型 `` の中には 「型」 しか渡せません。

そのため、値である関数から型を取り出す魔法のキーワード `typeof` を使って、`typeof createUser`(値から型空間への変換)としてあげる必要があります。この「値と型の境界線」を意識できるようになると、TypeScriptの理解は一気に深まりますよ。

—

3. 関数の引数型を抽出する「Parameters」

続いては、関数の引数を抜き出す `Parameters` です。

引数は「タプル型」として取り出される

`Parameters` の最大の特徴は、引数が複数ある場合でも 「タプル型(要素の型と順番が決まった配列型)」 として取り出される点です。

function sendEmail(to: string, subject: string, isHtml?: boolean): boolean {
console.log(`Sending to ${to}: ${subject}`);
return true;
}

// 💡 引数全体の型を抽出(タプル型になる)
type SendEmailArgs = Parameters;
// type SendEmailArgs = [to: string, subject: string, isHtml?: boolean | undefined]

// 💡 インデックスアクセス型を使って、特定の引数の型だけをピンポイントで取得
type TargetEmail = SendEmailArgs[0]; // string
type Subject = SendEmailArgs[1]; // string
type IsHtmlOption = SendEmailArgs[2]; // boolean | undefined

配列のインデックスのように `[0]` や `[1]` でアクセスすることで、「第1引数の型だけ欲しい」といった要望もスマートに解決できます。

—

4. 実践:元の関数を壊さない「高階関数(ラッパー)」を作ろう

それでは、この2つを組み合わせて、現場で非常によく使われる 「関数の実行ログを記録する汎用ラッパー関数」 を作ってみましょう。

「元の関数のシグネチャを一切壊さず、引数も戻り値も完全に透過させて型を保つ」という高度なテクニックです。

// 1. 元となるビジネスロジック関数
async function saveUserData(userId: string, data: { name: string; email: string }) {
// 疑似的な保存処理
return { success: true, updatedId: userId };
}

// 2. 任意の関数をラップして、実行時間をログ出力する高階関数
function withLogging any>(targetFn: T) {
return async (
…args: Parameters // 💡 元の関数の引数型をそのまま適用!
): Promise>> => { // 💡 元の関数の戻り値(Promiseの中身)を維持!
console.log(`[LOG] 処理開始: 引数 =`, args);
const start = performance.now();

// 元の関数を実行
const result = await targetFn(…args);

const end = performance.now();
console.log(`[LOG] 処理終了: 所要時間 = ${(end – start).toFixed(2)}ms`);

return result;
};
}

// 3. ラップした新しい関数を生成
const loggedSaveUserData = withLogging(saveUserData);

// 4. 実行してみる
// エディタ上では、引数名や型ヒント(userId: string, data: …)が完璧にサジェストされます!
loggedSaveUserData(“usr_123”, { name: “Alice”, email: “alice@example.com” });

このコードの何がすごいの?

`withLogging` は、どんな関数が渡されても、その関数の引数(`Parameters`)と戻り値(`ReturnType`)を自動で計算して型を付け直してくれます。

もし将来 `saveUserData` の引数が増えたり戻り値が変わったりしても、ラッパー側のコードを1文字も修正する必要がありません。完全な型のDRY(Don’t Repeat Yourself)が実現できていますね。

—

5. 一歩先へ:内部では何が起きているの?(アンダー・ザ・フード)

「なぜ `Parameters` や `ReturnType` は、こんな都合のいいことができるんだろう?」と疑問に思った方のために、TypeScriptの型定義の裏側(標準ライブラリの中身)を少しだけお見せします。

TypeScriptの `lib.es5.d.ts` の中では、これらは以下のように定義されています。

// TypeScript標準ライブラリの定義(簡略化版)
type ReturnType any> = T extends (…args: any) => infer R ? R : any;
type Parameters any> = T extends (…args: infer P) => any ? P : never;

ここで使われている `infer` というキーワードは、「TypeScriptコンパイラさん、ここに当てはまる型を推論して変数(RやP)に入れてください!」という命令です。

1. `T extends (…args: any) => infer R`
「もし `T` が関数型なら、その戻り値の型を `R` と名付けて抜き出してください」
2. `? R : any`
「抜き出せたらその `R` を返し、関数じゃないなら `any`(または `never`)にしてください」

普段はここまで意識しなくても大丈夫ですが、「コンパイラが関数の形状をパターンマッチングして型を抽出しているんだな」というイメージを持っておくと、高度な型定義に出会ったときも怖くなくなりますよ。

—

まとめ

今回は、関数の型を再利用するための2つの必須ユーティリティ型を学びました。

| ユーティリティ型 | 抽出するもの | 使いどころの例 |
| :— | :— | :— |
| `ReturnType` | 関数の戻り値の型 | ファクトリ関数やAPI関数のレスポンス型を再利用したいとき |
| `Parameters` | 関数の引数の型(タプル) | 既存関数をラップする関数や、イベントハンドラの引数型を合わせたいとき |

関数の実装そのものを「信頼できる唯一の情報源(Single Source of Truth)」として型を自動抽出するアプローチは、コードの二重管理をなくし、リファクタリングに強い堅牢な設計を生み出します。

まずはご自身のプロジェクトで、「同じようなインターフェースを手書きしている関数」がないか探してみてください。見つけたら、ぜひ `Parameters` や `ReturnType` でスマートに書き換えてみましょう!

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