ReturnType と Parameters で関数の型を抽出する
関数の戻り値や引数の型を取り出したいとき、ReturnType と Parameters が使える。既存の関数から型を導出することで、型定義の重複を避けられる。
ReturnType
ReturnType は関数型から戻り値の型を抽出する。
function createUser(name: string, age: number) {
return {
id: crypto.randomUUID(),
name,
age,
createdAt: new Date(),
};
}
type User = ReturnType<typeof createUser>;
// { id: string; name: string; age: number; createdAt: Date }関数の実装から型を導出できるため、型定義を別途書く必要がない。関数の戻り値が変更されれば、型も自動的に追従する。
typeof が必要な点に注意。ReturnType は関数の「型」を受け取るため、関数そのものではなく typeof 関数名 を渡す。
// 関数型を直接渡す場合
type StringParser = (input: string) => string[];
type ParseResult = ReturnType<StringParser>; // string[]Parameters
Parameters は関数型から引数の型をタプルとして抽出する。
function greet(name: string, age: number, greeting?: string) {
return `${greeting ?? "Hello"}, ${name}! You are ${age}.`;
}
type GreetParams = Parameters<typeof greet>;
// [name: string, age: number, greeting?: string]タプル型なので、インデックスでアクセスできる。
type FirstArg = Parameters<typeof greet>[0]; // string
type SecondArg = Parameters<typeof greet>[1]; // number関数の戻り値の型を抽出する。戻り値を他の関数で使いたいときに便利。
関数の引数の型をタプルで抽出する。同じ引数を受け取るラッパー関数を作るときに便利。
実践例:ラッパー関数
元の関数と同じ引数を受け取るラッパー関数を作るとき、Parameters が役立つ。
function fetchUser(id: string, options?: { cache: boolean }) {
// 実装
}
function fetchUserWithRetry(
...args: Parameters<typeof fetchUser>
): ReturnType<typeof fetchUser> {
for (let i = 0; i < 3; i++) {
try {
return fetchUser(...args);
} catch (e) {
if (i === 2) throw e;
}
}
throw new Error("Unreachable");
}元の関数のシグネチャが変わっても、ラッパー関数は自動的に追従する。
実践例:API クライアント
API エンドポイントごとに関数を定義し、その型を一括で抽出するパターン。
const api = {
getUser: (id: string) => fetch(`/users/${id}`).then(r => r.json()),
createUser: (name: string, email: string) =>
fetch("/users", {
method: "POST",
body: JSON.stringify({ name, email }),
}).then(r => r.json()),
};
type API = typeof api;
type GetUserParams = Parameters<API["getUser"]>; // [id: string]
type CreateUserParams = Parameters<API["createUser"]>; // [name: string, email: string]Awaited との組み合わせ
非同期関数の場合、ReturnType は Promise<T> を返す。中身の型を取り出すには Awaited を使う。
async function fetchData() {
const res = await fetch("/api/data");
return res.json() as Promise<{ items: string[] }>;
}
type RawReturn = ReturnType<typeof fetchData>;
// Promise<{ items: string[] }>
type Data = Awaited<ReturnType<typeof fetchData>>;
// { items: string[] }Promise<T> から T を取り出す。ネストした Promise も再帰的に解決する。TypeScript 4.5 で追加された。
Awaited<ReturnType<typeof 非同期関数>> は頻出する。API 呼び出しの結果型を取得するのに使う。
ConstructorParameters と InstanceType
クラス向けにも同様のユーティリティ型がある。
class User {
constructor(public name: string, public age: number) {}
}
type UserConstructorParams = ConstructorParameters<typeof User>;
// [name: string, age: number]
type UserInstance = InstanceType<typeof User>;
// UserConstructorParameters はコンストラクタの引数、InstanceType はインスタンスの型を取得する。
注意点
オーバーロードされた関数の場合、最後のシグネチャのみが抽出される。
function parse(input: string): string[];
function parse(input: number): number[];
function parse(input: string | number): string[] | number[] {
// 実装
}
type ParseReturn = ReturnType<typeof parse>;
// string[] | number[](最後のシグネチャ)複数のオーバーロードすべてを扱いたい場合は、別途対策が必要になる。
ReturnType と Parameters を使いこなすと、型定義の DRY(Don’t Repeat Yourself)を実現できる。既存の関数から型を導出する習慣をつけよう。