こんにちは!TypeScriptの世界へようこそ。今日は、皆さんがTypeScriptの型定義をよりスマートに、そして効率的に行うための、ちょっとした「魔法」のようなテクニックをご紹介します。
「すでに存在する関数の型を、別の場所で再利用したい!」
そう思ったことはありませんか?まさに「Don’t Repeat Yourself (DRY)」の原則に則って、型定義も重複を避けたいですよね。
今回は、TypeScriptが提供する強力な演算子である`typeof`を使って、既存の関数からそのシグネチャ(引数と戻り値の型)を抽出し、それを別の場所で活用する方法を、基礎から本質まで、優しく丁寧に解説していきます。ここをクリアすれば、TypeScriptの関数における型定義はバッチリマスターできますよ!
—
TypeScriptの基本をマスター!関数シグネチャをスマートに再利用する極意

(画像はイメージです)
1. なぜ関数シグネチャを抽出・再利用するのか?
TypeScriptを書いていると、同じような引数や戻り値の型を持つ関数を何度も定義することがあります。例えば、あるAPIクライアントの関数と、それをラップするロギング関数やキャッシュ関数を考えてみてください。
// 元のAPI呼び出し関数
function fetchUserData(userId: number, token: string): Promise<{ id: number; name: string }> {
// … ユーザーデータをフェッチする処理 …
console.log(`Fetching user data for userId: ${userId}`);
return Promise.resolve({ id: userId, name: `User-${userId}` });
}
// ユーザーデータを取得してログに出力する関数(元の関数と似たシグネチャ)
function logAndFetchUserData(userId: number, token: string): Promise<{ id: number; name: string }> {
console.log(`[LOG] Calling fetchUserData with userId: ${userId}`);
return fetchUserData(userId, token);
}
この例では、`fetchUserData`と`logAndFetchUserData`が全く同じ引数と戻り値の型を持っていますよね。もし`fetchUserData`の引数や戻り値の型が変わったら、`logAndFetchUserData`の型定義も手動で修正しなければなりません。これは手間ですし、ミスも起こりやすくなります。
このような「重複した型定義」を避けるために、既存の関数からそのシグネチャ(関数の「形」)を抽出し、再利用するテクニックが非常に役立ちます。
2. `typeof`演算子:値の世界から型を抽出する魔法
皆さんはJavaScriptで変数の型を調べる際に`typeof`演算子を使ったことがありますよね?
const myString = “hello”;
console.log(typeof myString); // “string”
const myNumber = 123;
console.log(typeof myNumber); // “number”
これは実行時に値の型を調べるためのJavaScriptの機能です。
しかし、TypeScriptではこの`typeof`が型レベルでも使えるんです!これは、値が持つ型を、そのまま型として抽出するための、まさに魔法のような機能なんです。
例えば、先ほどの`myString`の型を抽出してみましょう。
const myString = “hello TypeScript”;
type MyStringType = typeof myString; // ← ここで typeof を使います
// MyStringType は string 型と評価されます
// ホバーすると型ツールチップが表示されるでしょう
const anotherString: MyStringType = “TypeScript is fun!”;
// const errorString: MyStringType = 123; // エラー: Type ‘number’ is not assignable to type ‘string’.
ご覧の通り、`typeof myString`は、値`myString`が持っている型である`string`を抽出しています。この`typeof`演算子は、単なるプリミティブ型だけでなく、オブジェクトや関数など、あらゆる値の型を抽出できます。
イメージとしては、値の世界にある「データ」や「関数」の「設計図」を、型世界に持ち込むような感じです。

(値の世界にある具体的な値から、`typeof`を通じて型世界にその「形」を抽出するイメージ図)
3. 関数シグネチャを抽出する
では、本題です。この`typeof`を関数に適用するとどうなるでしょうか?
/
- 2つの数値を足し合わせる関数
- @param a – 最初の数値
- @param b – 2番目の数値
- @returns 合計値
/
function add(a: number, b: number): number {
return a + b;
}
// add関数の型シグネチャを抽出
type AddFunctionType = typeof add;
/
AddFunctionType の型は以下のようになります:
(a: number, b: number) => number
これは「number型の引数aとnumber型の引数bを受け取り、number型を返す関数」
という関数の形そのものを示しています。
/
// 抽出した型を使って、別の関数を定義してみましょう
const subtract: AddFunctionType = (x, y) => {
// `subtract` は `AddFunctionType` なので、引数や戻り値の型が自動的に推論されます
// x: number, y: number, 戻り値: number
return x – y;
};
// 型の不一致を試してみる
// const multiply: AddFunctionType = (x: string, y: number) => { // エラー: 型が合わない
// return x y;
// };
// const divide: AddFunctionType = (x, y) => {
// return `${x / y}`; // エラー: Type ‘string’ is not assignable to type ‘number’.
// };
console.log(subtract(10, 5)); // 5
このように、関数そのものに`typeof`を適用すると、その関数の完全な型シグネチャ(引数の型リストと戻り値の型)が抽出されます。そして、その抽出した型を別の関数の型定義に利用することで、型の一貫性を保ちながらDRYなコードを書くことができるのです。
4. `Parameters`と`ReturnType`:引数と戻り値だけをピンポイントで抽出
`typeof`で関数の型シグネチャ全体を抽出できるのは素晴らしいですが、「引数の型だけが欲しい」「戻り値の型だけが欲しい」というケースも多いですよね。TypeScriptには、このようなニーズに応えるためのユーティリティタイプ(Utility Types)が用意されています。
4.1 `Parameters`: 関数の引数の型を抽出する
`Parameters
function processOrder(orderId: string, quantity: number, customerName?: string): { success: boolean; message: string } {
// … 注文処理 …
console.log(`Processing order ${orderId} (Quantity: ${quantity}) for ${customerName || ‘Guest’}`);
return { success: true, message: `Order ${orderId} processed.` };
}
// processOrder関数の引数の型を抽出
type OrderParameters = Parameters
/
OrderParameters の型は以下のようになります:
[orderId: string, quantity: number, customerName?: string | undefined]
これは、string、number、string | undefined の3つの要素を持つタプル型です。
オプショナル引数も `| undefined` として型に含まれることに注目してください。
/
// 抽出した引数型を使って、新しい関数を定義する例
function validateOrder(…args: OrderParameters): boolean {
const [orderId, quantity, customerName] = args; // タプルなので分割代入が便利
if (!orderId || typeof quantity !== ‘number’ || quantity <= 0) {
console.error("Invalid order parameters.");
return false;
}
console.log(`Order ${orderId} parameters are valid.`);
return true;
}
validateOrder("A123", 5); // Output: Order A123 parameters are valid.
// validateOrder("B456", 0); // Output: Invalid order parameters.
// validateOrder("C789", 10, "Alice"); // Output: Order C789 parameters are valid.
// 部分的な引数の型を取り出したい場合は、タプル型へのインデックスアクセスを使います。
type OrderIdType = OrderParameters[0]; // string
type QuantityType = OrderParameters[1]; // number
type CustomerNameType = OrderParameters[2]; // string | undefined
`Parameters
4.2 `ReturnType`: 関数の戻り値の型を抽出する
`ReturnType
// 先ほどの processOrder 関数を再利用
// function processOrder(…) { … }
// processOrder関数の戻り値の型を抽出
type OrderResult = ReturnType
/
OrderResult の型は以下のようになります:
{ success: boolean; message: string }
/
// 抽出した戻り値型を使って、処理結果を扱う関数を定義する例
function displayOrderResult(result: OrderResult): void {
if (result.success) {
console.log(`✅ ${result.message}`);
} else {
console.error(`❌ エラー: ${result.message}`);
}
}
const result1 = processOrder(“D987”, 2);
displayOrderResult(result1); // Output: ✅ Order D987 processed.
// 非同期関数の戻り値の型を抽出する場合
async function fetchData(): Promise
return [“data1”, “data2”];
}
type FetchDataReturnType = ReturnType
// Promiseが解決された後の型(つまり string[])が欲しい場合は、
// さらに `Awaited` ユーティリティ型を使います(これは少し高度なトピックです)
type ResolvedFetchData = Awaited
`ReturnType
5. 陥りやすいポイントと注意点
5.1 変数に代入された関数やアロー関数
`typeof`は、`function`キーワードで定義された関数だけでなく、変数に代入されたアロー関数や匿名関数にもそのまま適用できます。
const calculateDiscount = (price: number, rate: number): number => {
return price (1 – rate);
};
type CalculateDiscountType = typeof calculateDiscount;
// 型は (price: number, rate: number) => number になります
const displayDiscount: CalculateDiscountType = (p, r) => {
console.log(`割引後の価格は: ${p (1 – r)}`);
return p (1 – r);
};
5.2 クラスのメソッドの型抽出
クラスのメソッドの型を抽出したい場合は、少し工夫が必要です。インスタンスメソッドと静的メソッドで方法が異なります。
class ProductService {
private products: { id: string; name: string; price: number }[] = [];
constructor() {
this.products.push({ id: “p1”, name: “Laptop”, price: 1200 });
this.products.push({ id: “p2”, name: “Mouse”, price: 25 });
}
// インスタンスメソッド
getProductById(id: string): typeof this.products[number] | undefined {
return this.products.find(p => p.id === id);
}
// 静的メソッド
static getServiceVersion(): string {
return “1.0.0”;
}
}
// インスタンスメソッドの型を抽出する方法
// 1. `typeof Class.prototype.methodName` を使う
type GetProductByIdMethodType = typeof ProductService.prototype.getProductById;
// 型は (id: string) => { id: string; name: string; price: number; } | undefined になります
// 2. インデックスアクセス型を使う(よりTypeScriptらしい書き方)
type ProductServiceInstance = ProductService; // まずインスタンスの型を取得
type GetProductByIdViaIndex = ProductServiceInstance[‘getProductById’];
// 型は上記と同じになります
// 静的メソッドの型を抽出する方法
// `typeof ClassName.staticMethodName` を使う
type GetServiceVersionType = typeof ProductService.getServiceVersion;
// 型は () => string になります
特にインスタンスメソッドの型抽出では、`typeof Class.prototype.methodName`または`ClassInstanceType[‘methodName’]`のどちらも使えます。後者のインデックスアクセス型は、オブジェクトのプロパティの型を抽出する一般的な方法なので、こちらに慣れておくと良いでしょう。
5.3 ジェネリック関数を扱う場合
ジェネリック関数(型引数を持つ関数)の型を抽出する場合、型引数が適切に推論されるか、明示的に指定されているかに注意が必要です。初学者のうちは深く考えすぎず、「ジェネリックな部分が`unknown`などになる可能性がある」とだけ覚えておきましょう。より高度な型操作では、`infer`キーワードと組み合わせて、ジェネリックな型引数を抽出することも可能です。
6. 実践的な応用例:関数ラッパーの型安全な定義
最後に、これまでの知識を組み合わせて、より実践的な例を見てみましょう。
既存の関数に前処理(ロギングなど)や後処理(エラーハンドリングなど)を追加する「関数ラッパー」を型安全に定義する例です。
/
- ユーザーIDからユーザー名を取得する非同期関数
/
async function fetchUserName(userId: number): Promise
console.log(`[API] ユーザーID ${userId} のユーザー名を取得中…`);
await new Promise(resolve => setTimeout(resolve, 500)); // 擬似的な遅延
if (userId === 999) {
throw new Error(“ユーザーが見つかりません”);
}
return `ユーザー名-${userId}`;
}
// 関数の型をラップするためのジェネリックなユーティリティ関数
// T は任意の関数型を表します
function createSafeFunction
func: T
): (…args: Parameters
// func: T の引数の型は Parameters
// ここでは非同期関数を想定し、Promiseの解決後の型を Awaited
return async (…args: Parameters
try {
console.log(`[Wrapper] 関数 ${func.name || “匿名関数”} を実行します。引数:`, args);
const result = await func(…args);
console.log(`[Wrapper] 関数 ${func.name || “匿名関数”} の実行が成功しました。`);
return result;
} catch (error: any) {
console.error(`[Wrapper] 関数 ${func.name || “匿名関数”} の実行中にエラーが発生しました:`, error.message);
return null; // エラー時は null を返す
}
};
}
// fetchUserName 関数を安全なラッパーで包む
const safeFetchUserName = createSafeFunction(fetchUserName);
// 実行してみましょう
(async () => {
console.log(“— 正常な呼び出し —“);
const userName1 = await safeFetchUserName(123);
if (userName1 !== null) {
console.log(`取得したユーザー名: ${userName1}`);
}
console.log(“\n— エラーになる呼び出し —“);
const userName2 = await safeFetchUserName(999);
if (userName2 === null) {
console.log(“ユーザー名の取得に失敗しました。”);
}
})();
/
実行結果例:
— 正常な呼び出し —
[Wrapper] 関数 fetchUserName を実行します。引数: [ 123 ]
[API] ユーザーID 123 のユーザー名を取得中…
[Wrapper] 関数 fetchUserName の実行が成功しました。
取得したユーザー名: ユーザー名-123
— エラーになる呼び出し —
[Wrapper] 関数 fetchUserName を実行します。引数: [ 999 ]
[API] ユーザーID 999 のユーザー名を取得中…
[Wrapper] 関数 fetchUserName の実行中にエラーが発生しました: ユーザーが見つかりません
ユーザー名の取得に失敗しました。
/
この例では、`createSafeFunction`がジェネリックな型`T`を受け取ることで、どんな関数に対してもその引数と戻り値の型を正しく継承したラッパー関数を生成しています。これにより、元の関数シグネチャを変更することなく、追加のロジックを安全に組み込むことができます。
まとめ:TypeScriptの型システムを掌握する第一歩
今回は、`typeof`演算子を使って既存の関数からそのシグネチャを抽出する方法、そして`Parameters`と`ReturnType`というユーティリティタイプで引数や戻り値の型をピンポイントで抽出する方法を学びました。
これらは、TypeScriptでDRY原則を徹底し、型安全なコードを書くための非常に強力なツールです。
- `typeof`: 値の世界から型世界へ、その「形」をそのまま持ち込む魔法のブリッジ。関数に適用すれば、完全な関数シグネチャを抽出できます。
- `Parameters
` : 関数の型`T`から、その引数リストをタプル型として抽出します。 - `ReturnType
` : 関数の型`T`から、その戻り値の型を抽出します。
これらのテクニックを使いこなすことで、皆さんのTypeScriptコードはより堅牢に、そして保守しやすくなるはずです。TypeScriptの型システムは奥深く、知れば知るほど新しい発見があります。今日の知識が、皆さんがTypeScriptを「掌握する」ための確かな一歩となることを願っています。
これからも一緒にTypeScriptの旅を楽しみましょう!