⚡
TypeScript Utility Types【関数・Promise編】
はじめに
この記事では、TypeScript の関数や Promise に特化した Utility Types を実例を交えて詳しく解説していきます。
関数に関する Utility Types
ReturnType<T> - 関数の戻り値の型を取得
ReturnType<T>
は、関数の戻り値の型を抽出します。
function getUser() {
return { name: "John", age: 30, email: "john@example.com" };
}
type User = ReturnType<typeof getUser>;
// User は { name: string; age: number; email: string; } になる
// 使用例:APIレスポンス型の統一
async function fetchUserData() {
const response = await fetch("/api/user");
return response.json();
}
type UserData = ReturnType<typeof fetchUserData>;
// UserData は Promise<any> になる
// より実践的な例
function createApiClient() {
return {
getUsers: () => Promise.resolve([{ id: 1, name: "Alice" }]),
getUser: (id: number) => Promise.resolve({ id, name: "Alice" }),
deleteUser: (id: number) => Promise.resolve({ success: true }),
};
}
type ApiClient = ReturnType<typeof createApiClient>;
// ApiClient は作成されたオブジェクトの型になる
Parameters<T> - 関数の引数の型をタプルで取得
Parameters<T>
は、関数の引数の型をタプルとして抽出します。
function createUser(name: string, age: number, email: string) {
return { name, age, email };
}
type CreateUserParams = Parameters<typeof createUser>;
// CreateUserParams は [string, number, string] になる
// 使用例:関数のラッパーを作る
function logAndCreateUser(...args: Parameters<typeof createUser>) {
console.log("Creating user with params:", args);
return createUser(...args);
}
// より複雑な例:汎用的なAPI呼び出し関数
async function apiCall(
endpoint: string,
method: "GET" | "POST" | "PUT" | "DELETE",
data?: any
) {
// API呼び出しロジック
return { success: true, data: null };
}
type ApiCallParams = Parameters<typeof apiCall>;
// [string, 'GET' | 'POST' | 'PUT' | 'DELETE', any?]
function createApiCallWrapper(defaultEndpoint: string) {
return function (method: ApiCallParams[1], data?: ApiCallParams[2]) {
return apiCall(defaultEndpoint, method, data);
};
}
ConstructorParameters<T> - コンストラクタの引数の型を取得
ConstructorParameters<T>
は、クラスのコンストラクタの引数の型をタプルとして抽出します。
class User {
constructor(public name: string, public age: number, public email: string) {}
}
type UserConstructorParams = ConstructorParameters<typeof User>;
// UserConstructorParams は [string, number, string] になる
// 使用例:ファクトリー関数
function createUser(...args: ConstructorParameters<typeof User>) {
return new User(...args);
}
// より実践的な例:依存注入
class DatabaseService {
constructor(private connectionString: string, private timeout: number) {}
}
class UserService {
constructor(
private db: DatabaseService,
private cacheTimeout: number = 300
) {}
}
type UserServiceDeps = ConstructorParameters<typeof UserService>;
// [DatabaseService, number?]
function createUserService(...deps: UserServiceDeps) {
return new UserService(...deps);
}
InstanceType<T> - コンストラクタの戻り値の型を取得
InstanceType<T>
は、コンストラクタ関数のインスタンスの型を抽出します。
class User {
constructor(public name: string, public age: number) {}
greet() {
return `Hello, I'm ${this.name}`;
}
}
type UserInstance = InstanceType<typeof User>;
// UserInstance は User になる
// 使用例:汎用的なインスタンス生成関数
function createInstance<T extends new (...args: any[]) => any>(
ctor: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
return new ctor(...args);
}
const user = createInstance(User, "Alice", 25);
// user の型は User
// より実践的な例:プラグインシステム
interface Plugin {
name: string;
execute(): void;
}
class LoggerPlugin implements Plugin {
constructor(public name: string) {}
execute() {
console.log(`[${this.name}] Logging...`);
}
}
class DatabasePlugin implements Plugin {
constructor(public name: string, private connectionString: string) {}
execute() {
console.log(`[${this.name}] Connecting to database...`);
}
}
type PluginConstructors = typeof LoggerPlugin | typeof DatabasePlugin;
type PluginInstance = InstanceType<PluginConstructors>;
// PluginInstance は LoggerPlugin | DatabasePlugin
NoInfer<T> - 型推論を防ぐ
NoInfer<T>
は、TypeScript 5.4 で追加された型で、型推論を防ぐために使用します。
// 問題のある例:TypeScriptは最後の引数から型を推論してしまう
function createConfig<T extends string>(
options: T[],
defaultOption: T // ここでTが推論されてしまう
) {
return { options, defaultOption };
}
// "blue"が存在しないため、Tが"red" | "green" | "blue"に拡張されてしまう
const config1 = createConfig(["red", "green"], "blue"); // エラーにならない
// NoInfer<T>を使用した解決例
function createConfigSafe<T extends string>(
options: T[],
defaultOption: NoInfer<T> // 型推論を防ぐ
) {
return { options, defaultOption };
}
// 今度は正しくエラーになる
const config2 = createConfigSafe(["red", "green"], "red"); // ✅ OK
// const config3 = createConfigSafe(["red", "green"], "blue"); // ❌ エラー
// 実用例:型安全な設定システム
interface ThemeConfig {
colors: string[];
defaultColor: string;
fonts: string[];
defaultFont: string;
}
function createTheme<TColors extends string, TFonts extends string>(
colors: TColors[],
defaultColor: NoInfer<TColors>,
fonts: TFonts[],
defaultFont: NoInfer<TFonts>
): ThemeConfig {
return {
colors,
defaultColor,
fonts,
defaultFont,
};
}
const theme = createTheme(
["red", "blue", "green"],
"red", // ✅ colors配列に存在する
["Arial", "Helvetica"],
"Arial" // ✅ fonts配列に存在する
);
// const invalidTheme = createTheme(
// ["red", "blue", "green"],
// "purple", // ❌ エラー: colors配列に存在しない
// ["Arial", "Helvetica"],
// "Times" // ❌ エラー: fonts配列に存在しない
// );
Promise に関する Utility Types
Awaited<T> - Promise の解決値の型を取得
Awaited<T>
は、Promise が解決する値の型を抽出します。ネストした Promise にも対応しています。
// 基本的な使用例
type Result = Awaited<Promise<string>>;
// Result は string になる
// ネストしたPromiseの場合
type NestedResult = Awaited<Promise<Promise<number>>>;
// NestedResult は number になる(ネストが解消される)
// 使用例:非同期関数の戻り値型を取得
async function fetchUserProfile() {
const response = await fetch("/api/user/profile");
return response.json() as {
id: number;
name: string;
avatar: string;
};
}
type UserProfile = Awaited<ReturnType<typeof fetchUserProfile>>;
// UserProfile は { id: number; name: string; avatar: string; } になる
// より複雑な例:複数の非同期処理の結果
async function fetchUserData() {
const [profile, settings, notifications] = await Promise.all([
fetch("/api/profile").then((r) => r.json()),
fetch("/api/settings").then((r) => r.json()),
fetch("/api/notifications").then((r) => r.json()),
]);
return { profile, settings, notifications };
}
type UserData = Awaited<ReturnType<typeof fetchUserData>>;
// UserData は各プロパティの型を持つオブジェクトになる
その他の関数関連 Utility Types
ThisParameterType<T> - 関数の this パラメータの型を取得
function greet(this: { name: string }, message: string) {
return `${this.name}: ${message}`;
}
type ThisType = ThisParameterType<typeof greet>;
// ThisType は { name: string } になる
OmitThisParameter<T> - 関数から this パラメータを除去
function greet(this: { name: string }, message: string) {
return `${this.name}: ${message}`;
}
type GreetWithoutThis = OmitThisParameter<typeof greet>;
// GreetWithoutThis は (message: string) => string になる
実践的な使用例
型安全なイベントハンドラー
interface EventMap {
click: { x: number; y: number };
keydown: { key: string; ctrlKey: boolean };
resize: { width: number; height: number };
}
type EventListener<T extends keyof EventMap> = (event: EventMap[T]) => void;
class EventEmitter {
on<T extends keyof EventMap>(event: T, listener: EventListener<T>): void {
// イベントリスナーの登録
}
emit<T extends keyof EventMap>(
event: T,
data: Parameters<EventListener<T>>[0]
): void {
// イベントの発行
}
}
// 使用例
const emitter = new EventEmitter();
emitter.on("click", ({ x, y }) => {
console.log(`Clicked at ${x}, ${y}`);
});
emitter.emit("click", { x: 100, y: 200 }); // ✅ 型安全
// emitter.emit('click', { x: 100 }); // ❌ エラー: y プロパティがない
型安全な API クライアント
interface ApiEndpoints {
"/users": {
GET: { users: Array<{ id: number; name: string }> };
POST: { user: { id: number; name: string } };
};
"/users/:id": {
GET: { user: { id: number; name: string; email: string } };
PUT: { user: { id: number; name: string; email: string } };
DELETE: { success: boolean };
};
}
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiResponse<
TEndpoint extends keyof ApiEndpoints,
TMethod extends HttpMethod
> = TMethod extends keyof ApiEndpoints[TEndpoint]
? ApiEndpoints[TEndpoint][TMethod]
: never;
async function apiCall<
TEndpoint extends keyof ApiEndpoints,
TMethod extends HttpMethod
>(
endpoint: TEndpoint,
method: TMethod,
data?: any
): Promise<ApiResponse<TEndpoint, TMethod>> {
// API呼び出しの実装
return {} as any;
}
// 使用例
const userList = await apiCall("/users", "GET");
// userList の型は { users: Array<{ id: number; name: string }> }
const userDetail = await apiCall("/users/:id", "GET");
// userDetail の型は { user: { id: number; name: string; email: string } }
まとめ
この関数・Promise 編では、関数や Promise に特化した 8 つの Utility Types を紹介しました:
-
ReturnType<T>
- 関数の戻り値の型を取得 -
Parameters<T>
- 関数の引数の型をタプルで取得 -
ConstructorParameters<T>
- コンストラクタの引数の型を取得 -
InstanceType<T>
- コンストラクタの戻り値の型を取得 -
NoInfer<T>
- 型推論を防ぐ(TypeScript 5.4 以降) -
Awaited<T>
- Promise の解決値の型を取得 -
ThisParameterType<T>
- 関数の this パラメータの型を取得 -
OmitThisParameter<T>
- 関数から this パラメータを除去
これらを活用することで、関数や Promise を扱う際の型安全性を大幅に向上させることができます。
参考文献
Discussion