TypeScriptで型を定義していると、「既存の型の一部だけを使いたい」「すべてのプロパティをオプショナルにしたい」といった場面に頻繁に遭遇します。このような型変換を手作業で行うのは非効率であり、コードの保守性も低下します。
本記事では、TypeScriptが標準で提供する**ユーティリティ型(Utility Types)**を網羅的に解説します。ユーティリティ型を活用することで、既存の型から新しい型を効率的に生成でき、型定義の重複を大幅に削減できます。
この記事で学べること#
- TypeScriptユーティリティ型の基本概念と使いどころ
- Partial、Required、Readonlyによるプロパティ修飾子の操作
- Pick、Omitによるプロパティの選択と除外
- Record、Exclude、Extract、NonNullableによる型の構築と絞り込み
- ReturnType、Parametersによる関数型の抽出
- 実務で役立つユーティリティ型の組み合わせパターン
前提条件#
本記事のコード例を実行するには、以下の環境が必要です。
ローカル環境でサンプルコードを試す場合は、以下のコマンドでTypeScriptをインストールしてください。
1
|
npm install -g typescript
|
TypeScriptユーティリティ型とは#
TypeScriptユーティリティ型は、既存の型を変換して新しい型を生成するための組み込み型です。TypeScriptのlib.es5.d.tsなどに定義されており、追加のインストールなしで利用できます。
ユーティリティ型を使用するメリットは以下のとおりです。
- 型定義の重複を排除:同じ型を複数回定義する必要がなくなる
- 保守性の向上:元の型が変更されると、派生した型も自動的に更新される
- 可読性の向上:意図が明確な型名を使用できる
- 型安全性の維持:手動での型変換によるミスを防げる
以下のMermaid図は、主要なユーティリティ型の分類を示しています。
graph TB
subgraph "プロパティ修飾子の操作"
A[Partial] --> |すべてオプショナル| B[Type?]
C[Required] --> |すべて必須| D[Type]
E[Readonly] --> |すべて読み取り専用| F[readonly Type]
end
subgraph "プロパティの選択・除外"
G[Pick] --> |指定プロパティを選択| H[Selected Props]
I[Omit] --> |指定プロパティを除外| J[Remaining Props]
end
subgraph "型の構築・絞り込み"
K[Record] --> |キーと値の型を指定| L[Object Type]
M[Exclude] --> |ユニオンから除外| N[Filtered Union]
O[Extract] --> |ユニオンから抽出| P[Extracted Union]
Q[NonNullable] --> |null/undefined除外| R[Non-null Type]
end
subgraph "関数型の操作"
S[ReturnType] --> |戻り値の型を取得| T[Return Type]
U[Parameters] --> |引数の型を取得| V[Param Tuple]
endPartial - すべてのプロパティをオプショナルにする#
Partial<Type>は、指定した型のすべてのプロパティをオプショナル(省略可能)に変換します。部分的な更新処理を実装する際に特に有用です。
Partialの基本構文#
1
2
3
|
type Partial<T> = {
[P in keyof T]?: T[P];
};
|
Partialの使用例#
以下の例では、ユーザー情報の部分更新を型安全に実装しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial<User>は以下と同等
// {
// id?: number;
// name?: string;
// email?: string;
// age?: number;
// }
function updateUser(userId: number, updates: Partial<User>): User {
const currentUser = getUserById(userId);
return { ...currentUser, ...updates };
}
// 一部のプロパティのみ更新可能
updateUser(1, { name: "田中太郎" }); // OK
updateUser(1, { email: "tanaka@example.com", age: 30 }); // OK
updateUser(1, {}); // OK - 空の更新も許容
|
Partialの実践的なユースケース#
フォームの部分的なバリデーションや、API のPATCHリクエストのペイロード定義に活用できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
interface FormData {
username: string;
password: string;
confirmPassword: string;
}
// 部分的なバリデーションエラー
type ValidationErrors = Partial<Record<keyof FormData, string>>;
const errors: ValidationErrors = {
password: "パスワードは8文字以上必要です",
// username, confirmPasswordはエラーなしのため省略可能
};
|
Required - すべてのプロパティを必須にする#
Required<Type>は、Partialの逆で、すべてのオプショナルプロパティを必須に変換します。
Requiredの基本構文#
1
2
3
|
type Required<T> = {
[P in keyof T]-?: T[P];
};
|
-?は、オプショナル修飾子を除去することを意味します。
Requiredの使用例#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
interface Config {
apiUrl?: string;
timeout?: number;
retryCount?: number;
}
// Required<Config>は以下と同等
// {
// apiUrl: string;
// timeout: number;
// retryCount: number;
// }
function initializeApp(config: Required<Config>): void {
console.log(`API URL: ${config.apiUrl}`);
console.log(`Timeout: ${config.timeout}ms`);
console.log(`Retry Count: ${config.retryCount}`);
}
// すべてのプロパティが必須
initializeApp({
apiUrl: "https://api.example.com",
timeout: 5000,
retryCount: 3,
});
// エラー: timeoutが不足
// initializeApp({
// apiUrl: "https://api.example.com",
// retryCount: 3,
// });
|
Requiredの実践的なユースケース#
デフォルト値を適用した後の設定オブジェクトを型安全に扱う場合に有用です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
interface Options {
debug?: boolean;
verbose?: boolean;
maxConnections?: number;
}
const defaultOptions: Required<Options> = {
debug: false,
verbose: false,
maxConnections: 10,
};
function mergeOptions(userOptions: Options): Required<Options> {
return { ...defaultOptions, ...userOptions };
}
const finalOptions = mergeOptions({ debug: true });
// finalOptions.maxConnectionsは必ずnumber型
console.log(finalOptions.maxConnections.toFixed(0)); // 型安全にアクセス可能
|
Readonly - すべてのプロパティを読み取り専用にする#
Readonly<Type>は、すべてのプロパティを読み取り専用(変更不可)に変換します。イミュータブルなデータ構造を表現する際に使用します。
Readonlyの基本構文#
1
2
3
|
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
|
Readonlyの使用例#
1
2
3
4
5
6
7
8
9
10
11
12
|
interface Point {
x: number;
y: number;
}
const origin: Readonly<Point> = { x: 0, y: 0 };
// エラー: 読み取り専用プロパティに代入できない
// origin.x = 10;
// 新しいオブジェクトを作成する必要がある
const movedPoint: Readonly<Point> = { ...origin, x: 10 };
|
Readonlyの注意点#
Readonly<T>は**浅い(シャロー)**読み取り専用です。ネストしたオブジェクトは変更可能なままです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
interface DeepObject {
level1: {
level2: {
value: number;
};
};
}
const obj: Readonly<DeepObject> = {
level1: {
level2: {
value: 42,
},
},
};
// エラー: level1は変更不可
// obj.level1 = { level2: { value: 100 } };
// OK: ネストしたプロパティは変更可能(注意が必要)
obj.level1.level2.value = 100;
|
深い読み取り専用が必要な場合は、再帰的な型定義を使用します。
1
2
3
|
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
|
Pick - 特定のプロパティを選択する#
Pick<Type, Keys>は、指定したプロパティのみを含む新しい型を生成します。大きな型から必要な部分だけを抽出する際に使用します。
Pickの基本構文#
1
2
3
|
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
|
Pickの使用例#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
interface Article {
id: number;
title: string;
content: string;
author: string;
createdAt: Date;
updatedAt: Date;
tags: string[];
}
// 記事一覧で表示する項目のみ抽出
type ArticlePreview = Pick<Article, "id" | "title" | "author" | "createdAt">;
// ArticlePreviewは以下と同等
// {
// id: number;
// title: string;
// author: string;
// createdAt: Date;
// }
function renderArticleList(articles: ArticlePreview[]): void {
articles.forEach((article) => {
console.log(`${article.title} by ${article.author}`);
});
}
|
Pickの実践的なユースケース#
APIレスポンスから必要なフィールドのみを型定義する場合に有効です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
interface ApiResponse {
data: unknown;
status: number;
statusText: string;
headers: Record<string, string>;
config: object;
request: object;
}
// 必要なフィールドのみ抽出
type SimpleResponse = Pick<ApiResponse, "data" | "status">;
async function fetchData(): Promise<SimpleResponse> {
const response = await fetch("/api/data");
return {
data: await response.json(),
status: response.status,
};
}
|
Omit - 特定のプロパティを除外する#
Omit<Type, Keys>は、Pickの逆で、指定したプロパティを除外した新しい型を生成します。
Omitの基本構文#
1
|
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
|
Omitの使用例#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// パスワードを除外した公開用ユーザー型
type PublicUser = Omit<User, "password">;
// PublicUserは以下と同等
// {
// id: number;
// name: string;
// email: string;
// createdAt: Date;
// }
function getPublicProfile(user: User): PublicUser {
const { password, ...publicData } = user;
return publicData;
}
|
Omitの実践的なユースケース#
新規作成時にIDや自動生成フィールドを除外する場合に活用できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
interface Todo {
id: number;
title: string;
completed: boolean;
createdAt: Date;
updatedAt: Date;
}
// 作成時にはid, createdAt, updatedAtは不要
type CreateTodoInput = Omit<Todo, "id" | "createdAt" | "updatedAt">;
function createTodo(input: CreateTodoInput): Todo {
return {
...input,
id: generateId(),
createdAt: new Date(),
updatedAt: new Date(),
};
}
// 使用例
createTodo({
title: "TypeScriptを学ぶ",
completed: false,
});
|
Record - キーと値の型を指定してオブジェクト型を構築する#
Record<Keys, Type>は、指定したキーの型と値の型を持つオブジェクト型を生成します。辞書型やマップ型のデータ構造を表現する際に使用します。
Recordの基本構文#
1
2
3
|
type Record<K extends keyof any, T> = {
[P in K]: T;
};
|
Recordの使用例#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
type Status = "pending" | "approved" | "rejected";
interface StatusInfo {
label: string;
color: string;
}
// すべてのStatusに対してStatusInfoを定義
const statusMap: Record<Status, StatusInfo> = {
pending: { label: "保留中", color: "#FFA500" },
approved: { label: "承認済み", color: "#00FF00" },
rejected: { label: "却下", color: "#FF0000" },
};
function getStatusLabel(status: Status): string {
return statusMap[status].label;
}
|
Recordの実践的なユースケース#
ページごとのコンポーネント設定やルーティング定義に活用できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
type PageName = "home" | "about" | "contact" | "products";
interface PageConfig {
title: string;
requiresAuth: boolean;
layout: "default" | "fullwidth";
}
const pageConfigs: Record<PageName, PageConfig> = {
home: { title: "ホーム", requiresAuth: false, layout: "default" },
about: { title: "会社概要", requiresAuth: false, layout: "fullwidth" },
contact: { title: "お問い合わせ", requiresAuth: false, layout: "default" },
products: { title: "製品一覧", requiresAuth: true, layout: "default" },
};
|
Exclude - ユニオン型から特定の型を除外する#
Exclude<UnionType, ExcludedMembers>は、ユニオン型から指定した型を除外します。
Excludeの基本構文#
1
|
type Exclude<T, U> = T extends U ? never : T;
|
Excludeの使用例#
1
2
3
4
5
6
7
8
9
10
11
12
13
|
type AllColors = "red" | "green" | "blue" | "yellow" | "purple";
type PrimaryColors = "red" | "green" | "blue";
// 二次色のみを抽出
type SecondaryColors = Exclude<AllColors, PrimaryColors>;
// SecondaryColors = "yellow" | "purple"
type EventType = "click" | "scroll" | "keydown" | "keyup" | "focus" | "blur";
type KeyboardEvents = "keydown" | "keyup";
// キーボードイベント以外
type NonKeyboardEvents = Exclude<EventType, KeyboardEvents>;
// NonKeyboardEvents = "click" | "scroll" | "focus" | "blur"
|
Excludeの実践的なユースケース#
特定の条件を満たさない型を除外する場合に使用します。
1
2
3
4
5
6
7
8
|
type ApiResponse<T> =
| { status: "success"; data: T }
| { status: "error"; message: string }
| { status: "loading" };
// エラーとローディング状態を除外
type SuccessResponse<T> = Exclude<ApiResponse<T>, { status: "error" } | { status: "loading" }>;
// SuccessResponse<T> = { status: "success"; data: T }
|
Extract<Type, Union>は、Excludeの逆で、ユニオン型から指定した型に一致するものだけを抽出します。
1
|
type Extract<T, U> = T extends U ? T : never;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
type AllTypes = string | number | boolean | null | undefined;
// プリミティブ型のみ抽出
type PrimitiveTypes = Extract<AllTypes, string | number | boolean>;
// PrimitiveTypes = string | number | boolean
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
| { kind: "triangle"; base: number; height: number };
// 円形のみ抽出
type Circle = Extract<Shape, { kind: "circle" }>;
// Circle = { kind: "circle"; radius: number }
|
1
2
3
4
5
6
7
8
9
|
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
// 安全なメソッド(リソースを変更しない)
type SafeMethods = Extract<HttpMethod, "GET" | "HEAD" | "OPTIONS">;
// SafeMethods = "GET" | "HEAD" | "OPTIONS"
// 危険なメソッド(リソースを変更する可能性がある)
type UnsafeMethods = Exclude<HttpMethod, SafeMethods>;
// UnsafeMethods = "POST" | "PUT" | "PATCH" | "DELETE"
|
NonNullable - nullとundefinedを除外する#
NonNullable<Type>は、型からnullとundefinedを除外します。
NonNullableの基本構文#
1
|
type NonNullable<T> = T extends null | undefined ? never : T;
|
NonNullableの使用例#
1
2
3
4
5
6
7
8
9
10
11
|
type MaybeString = string | null | undefined;
// NonNullable<MaybeString> = string
const processString = (value: NonNullable<MaybeString>): string => {
return value.toUpperCase(); // 型安全にメソッドを呼び出せる
};
// 使用例
const result = processString("hello"); // OK
// processString(null); // エラー
// processString(undefined); // エラー
|
NonNullableの実践的なユースケース#
オプショナルチェーンやnullチェック後の型として使用します。
1
2
3
4
5
6
7
8
9
10
11
12
|
interface UserProfile {
id: number;
name: string;
avatar?: string | null;
}
function getAvatarUrl(user: UserProfile): NonNullable<UserProfile["avatar"]> | string {
if (user.avatar) {
return user.avatar; // この時点でstring型
}
return "/default-avatar.png";
}
|
ReturnType - 関数の戻り値の型を取得する#
ReturnType<Type>は、関数型の戻り値の型を抽出します。
ReturnTypeの基本構文#
1
|
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
|
ReturnTypeの使用例#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
function createUser(name: string, age: number) {
return {
id: Math.random(),
name,
age,
createdAt: new Date(),
};
}
// 関数の戻り値から型を推論
type User = ReturnType<typeof createUser>;
// User = {
// id: number;
// name: string;
// age: number;
// createdAt: Date;
// }
// 推論した型を別の場所で使用
function processUser(user: User): void {
console.log(`${user.name} (${user.age}歳)`);
}
|
ReturnTypeの実践的なユースケース#
サードパーティライブラリの関数の戻り値型を取得する場合に有用です。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 外部ライブラリの関数(型定義が複雑な場合)
declare function fetchUserData(): Promise<{
user: { id: number; name: string };
metadata: { lastLogin: Date };
}>;
// 戻り値の型を抽出
type FetchUserDataResult = ReturnType<typeof fetchUserData>;
// FetchUserDataResult = Promise<{ user: {...}; metadata: {...} }>
// Promiseの中身を取得(Awaited と組み合わせ)
type UserData = Awaited<ReturnType<typeof fetchUserData>>;
// UserData = { user: { id: number; name: string }; metadata: { lastLogin: Date } }
|
Parameters - 関数の引数の型を取得する#
Parameters<Type>は、関数型の引数の型をタプルとして抽出します。
Parametersの基本構文#
1
|
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
|
Parametersの使用例#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function greet(name: string, age: number, isVip: boolean): string {
return `Hello, ${name}! You are ${age} years old.${isVip ? " Welcome, VIP!" : ""}`;
}
// 関数の引数型をタプルとして取得
type GreetParams = Parameters<typeof greet>;
// GreetParams = [name: string, age: number, isVip: boolean]
// スプレッド構文で使用
function wrapGreet(...args: GreetParams): string {
console.log("Greeting with:", args);
return greet(...args);
}
wrapGreet("田中", 30, true);
|
Parametersの実践的なユースケース#
関数のラッパーやデコレータを作成する際に活用できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
function withLogging<T extends (...args: any[]) => any>(
fn: T
): (...args: Parameters<T>) => ReturnType<T> {
return (...args: Parameters<T>): ReturnType<T> => {
console.log(`Calling function with args:`, args);
const result = fn(...args);
console.log(`Function returned:`, result);
return result;
};
}
function add(a: number, b: number): number {
return a + b;
}
const loggedAdd = withLogging(add);
loggedAdd(2, 3); // ログ出力後に5を返す
|
ユーティリティ型の組み合わせパターン#
実務では、複数のユーティリティ型を組み合わせて使用することが多くあります。以下に代表的なパターンを紹介します。
パターン1: 部分的な更新用の型#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
stock: number;
}
// idは変更不可、その他は部分的に更新可能
type UpdateProductInput = Partial<Omit<Product, "id">> & Pick<Product, "id">;
function updateProduct(input: UpdateProductInput): Product {
const { id, ...updates } = input;
const current = getProductById(id);
return { ...current, ...updates };
}
// 使用例
updateProduct({
id: 1, // 必須
price: 1500, // オプション
stock: 100, // オプション
});
|
パターン2: 読み取り専用のデータベースエンティティ#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
interface Entity {
id: number;
createdAt: Date;
updatedAt: Date;
}
interface Article extends Entity {
title: string;
content: string;
authorId: number;
}
// 作成時の入力型(自動生成フィールドを除外)
type CreateArticleInput = Omit<Article, keyof Entity>;
// 読み取り専用のエンティティ型
type ReadonlyArticle = Readonly<Article>;
// 部分更新型(idと日付は除外、残りはオプション)
type UpdateArticleInput = Partial<Omit<Article, keyof Entity>>;
|
パターン3: APIレスポンスの型定義#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
interface ApiSuccess<T> {
status: "success";
data: T;
timestamp: Date;
}
interface ApiError {
status: "error";
code: string;
message: string;
timestamp: Date;
}
type ApiResponse<T> = ApiSuccess<T> | ApiError;
// 成功レスポンスのみ抽出
type SuccessOnly<T> = Extract<ApiResponse<T>, { status: "success" }>;
// エラーレスポンスのみ抽出
type ErrorOnly = Extract<ApiResponse<unknown>, { status: "error" }>;
// データ型のみ取得
type DataType<T> = SuccessOnly<T>["data"];
|
ユーティリティ型一覧表#
以下の表は、本記事で解説したTypeScriptユーティリティ型の一覧です。
| ユーティリティ型 |
説明 |
リリースバージョン |
Partial<T> |
すべてのプロパティをオプショナルにする |
2.1 |
Required<T> |
すべてのプロパティを必須にする |
2.8 |
Readonly<T> |
すべてのプロパティを読み取り専用にする |
2.1 |
Pick<T, K> |
指定したプロパティのみを選択する |
2.1 |
Omit<T, K> |
指定したプロパティを除外する |
3.5 |
Record<K, T> |
キーと値の型を指定してオブジェクト型を構築する |
2.1 |
Exclude<T, U> |
ユニオン型から指定した型を除外する |
2.8 |
Extract<T, U> |
ユニオン型から指定した型を抽出する |
2.8 |
NonNullable<T> |
nullとundefinedを除外する |
2.8 |
ReturnType<T> |
関数の戻り値の型を取得する |
2.8 |
Parameters<T> |
関数の引数の型をタプルとして取得する |
3.1 |
まとめ#
本記事では、TypeScriptの組み込みユーティリティ型について網羅的に解説しました。
ユーティリティ型を活用することで、以下のメリットが得られます。
- 型定義の重複を排除し、DRY(Don’t Repeat Yourself)原則を守れる
- 元の型との整合性が自動的に維持される
- 意図が明確な型名により、コードの可読性が向上する
- 型安全性を保ちながら、柔軟な型変換が可能になる
これらのユーティリティ型は、日々のTypeScript開発において非常に頻繁に使用されます。まずはPartial、Pick、Omitの3つから使い始め、徐々に他のユーティリティ型も活用していくことをおすすめします。
参考リンク#