TypeScriptには、既存の型を変換・加工するための「ユーティリティ型」が標準で多数用意されています。これらを活用することで、冗長な型定義を減らし、安全かつ保守性の高いコードを書くことができます。
本記事では、Partial・Required・Pick・Omit・Recordをはじめとする主要なユーティリティ型を、実務のユースケースとコード例を交えて徹底解説します。
ユーティリティ型とは何か
ユーティリティ型(Utility Types)とは、TypeScriptが標準で提供する型を変換するためのジェネリック型です。既存の型を元に、プロパティをオプショナルにしたり、一部だけを抽出したりと、さまざまな型操作を簡潔に行えます。
たとえば、ユーザー情報の型を定義した後、更新用のフォームでは一部の項目だけを変更したいケースがあります。こうした場面で毎回新しいインターフェースを定義するのは非効率です。ユーティリティ型を使えば、元の型から必要な形へ簡単に変換できます。
なぜユーティリティ型が重要なのか
ユーティリティ型を使う主な利点は以下の3点です。
1. DRY原則の遵守:同じプロパティ定義を複数箇所に書く必要がなくなり、型の一元管理が可能になります。
2. 型安全性の向上:元の型が変更された場合、派生型も自動的に追従するため、型の不整合によるバグを防げます。
3. コードの可読性:「この型はUserから一部を抜き出したもの」という意図が型定義から読み取れるため、チーム開発での理解が容易になります。
基本となる型定義の準備
以降の解説で使用する基本の型を定義しておきます。
interface User {
id: number;
name: string;
email: string;
age: number;
role: "admin" | "editor" | "viewer";
createdAt: Date;
updatedAt: Date;
}
interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
inStock: boolean;
}
Partial・Required・Readonlyの使い方
まずは、プロパティの修飾子を一括で変更するユーティリティ型から見ていきましょう。
Partial<T>:全プロパティをオプショナルにする
Partial<T>は、型Tのすべてのプロパティを省略可能(optional)にします。更新処理のように、一部の項目だけを変更するケースで非常に便利です。
// Partial<User> はすべてのプロパティが ? 付きになる
type UserUpdateInput = Partial<User>;
// 以下と同等
// interface UserUpdateInput {
// id?: number;
// name?: string;
// email?: string;
// age?: number;
// role?: "admin" | "editor" | "viewer";
// createdAt?: Date;
// updatedAt?: Date;
// }
function updateUser(id: number, updates: Partial<User>): User {
const currentUser = getUserById(id);
return { ...currentUser, ...updates, updatedAt: new Date() };
}
// 名前だけ更新
updateUser(1, { name: "新しい名前" });
// メールとロールだけ更新
updateUser(1, { email: "new@example.com", role: "editor" });
Partialを使わない場合、更新のたびにすべてのプロパティを渡す必要があり、実用的ではありません。
Required<T>:全プロパティを必須にする
Required<T>はPartialの逆で、すべてのプロパティを必須にします。オプショナルなプロパティを含む型から、完全な型を作りたい場合に使います。
interface Config {
host?: string;
port?: number;
debug?: boolean;
logLevel?: "info" | "warn" | "error";
}
// すべてが必須になる
type FullConfig = Required<Config>;
function initializeApp(config: FullConfig): void {
console.log(`Starting on ${config.host}:${config.port}`);
console.log(`Debug: ${config.debug}, Log: ${config.logLevel}`);
}
// デフォルト値とマージして Required を満たす
function createConfig(partial: Config): FullConfig {
return {
host: partial.host ?? "localhost",
port: partial.port ?? 3000,
debug: partial.debug ?? false,
logLevel: partial.logLevel ?? "info",
};
}
Readonly<T>:全プロパティを読み取り専用にする
Readonly<T>は、すべてのプロパティにreadonlyを付与します。イミュータブルなデータを扱う場合や、意図しない変更を防ぎたい場合に有効です。
type ImmutableUser = Readonly<User>;
const user: ImmutableUser = {
id: 1,
name: "田中太郎",
email: "tanaka@example.com",
age: 30,
role: "admin",
createdAt: new Date(),
updatedAt: new Date(),
};
// user.name = "佐藤花子"; // エラー: readonly プロパティに代入不可
// Redux や状態管理で活用
interface AppState {
users: User[];
currentUser: User | null;
isLoading: boolean;
}
type ReadonlyState = Readonly<AppState>;
function reducer(state: ReadonlyState, action: Action): ReadonlyState {
// state.isLoading = true; // エラーになるため安全
return { ...state, isLoading: true };
}
Pick・Omitによるプロパティの選択と除外
型の一部だけを取り出したい場面は実務で頻繁に発生します。PickとOmitはその代表的な手段です。
Pick<T, K>:必要なプロパティだけを抽出する
Pick<T, K>は、型Tから指定したキーKのプロパティだけを抽出した新しい型を作ります。
// ユーザー一覧表示に必要な情報だけ抽出
type UserListItem = Pick<User, "id" | "name" | "email" | "role">;
// APIレスポンスの型定義
function fetchUserList(): Promise<UserListItem[]> {
return fetch("/api/users").then((res) => res.json());
}
// フォーム入力に必要なフィールドだけ抽出
type UserFormData = Pick<User, "name" | "email" | "age" | "role">;
function UserForm({ onSubmit }: { onSubmit: (data: UserFormData) => void }) {
const handleSubmit = (formData: UserFormData) => {
// id, createdAt, updatedAt は含まれないので安全
onSubmit(formData);
};
}
// 商品の価格情報だけ抽出
type ProductPricing = Pick<Product, "id" | "name" | "price">;
Omit<T, K>:不要なプロパティを除外する
Omit<T, K>はPickの逆で、指定したキーを除外した型を作ります。「これ以外は全部必要」という場合に便利です。
// 新規作成時はid, createdAt, updatedAtは不要
type UserCreateInput = Omit<User, "id" | "createdAt" | "updatedAt">;
async function createUser(input: UserCreateInput): Promise<User> {
const newUser: User = {
...input,
id: generateId(),
createdAt: new Date(),
updatedAt: new Date(),
};
await saveToDatabase(newUser);
return newUser;
}
// 使用例
const newUser: UserCreateInput = {
name: "鈴木一郎",
email: "suzuki@example.com",
age: 25,
role: "viewer",
};
// PickとOmitの組み合わせ
// パスワードを含む内部型から、APIレスポンス用の安全な型を作る
interface UserInternal extends User {
passwordHash: string;
lastLoginIp: string;
}
type UserPublic = Omit<UserInternal, "passwordHash" | "lastLoginIp">;
PickとOmitの使い分け
どちらを使うかは、「残したいプロパティ」と「除外したいプロパティ」のどちらが少ないかで判断します。
// プロパティが多い型から少数だけ使いたい → Pick
type UserName = Pick<User, "id" | "name">;
// プロパティが多い型から少数だけ除外したい → Omit
type UserWithoutTimestamps = Omit<User, "createdAt" | "updatedAt">;
Record・Extract・Excludeの活用
キーと値の型を指定してオブジェクト型を作るRecordや、ユニオン型を操作するExtract・Excludeも実務で頻繁に使います。
Record<K, T>:キーと値の型を指定する
Record<K, T>は、キーの型がK、値の型がTであるオブジェクト型を作ります。
// ロールごとの権限マッピング
type Role = "admin" | "editor" | "viewer";
interface Permission {
canCreate: boolean;
canRead: boolean;
canUpdate: boolean;
canDelete: boolean;
}
const rolePermissions: Record<Role, Permission> = {
admin: { canCreate: true, canRead: true, canUpdate: true, canDelete: true },
editor: { canCreate: true, canRead: true, canUpdate: true, canDelete: false },
viewer: { canCreate: false, canRead: true, canUpdate: false, canDelete: false },
};
// HTTPステータスコードとメッセージのマッピング
type StatusCode = 200 | 400 | 401 | 403 | 404 | 500;
const statusMessages: Record<StatusCode, string> = {
200: "OK",
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Not Found",
500: "Internal Server Error",
};
// 動的なキーを持つオブジェクト
type UserScores = Record<string, number>;
const scores: UserScores = {
math: 85,
english: 92,
science: 78,
};
Extract<T, U>とExclude<T, U>:ユニオン型のフィルタリング
Extractはユニオン型から特定の型を抽出し、Excludeは特定の型を除外します。
type AllEvents = "click" | "scroll" | "mousemove" | "keydown" | "keyup" | "load";
// マウス関連のイベントだけ抽出
type MouseEvents = Extract<AllEvents, "click" | "scroll" | "mousemove">;
// "click" | "scroll" | "mousemove"
// キーボードイベントを除外
type NonKeyboardEvents = Exclude<AllEvents, "keydown" | "keyup">;
// "click" | "scroll" | "mousemove" | "load"
// 実務での活用例:APIエンドポイントの分類
type Endpoint =
| { method: "GET"; path: string }
| { method: "POST"; path: string; body: unknown }
| { method: "PUT"; path: string; body: unknown }
| { method: "DELETE"; path: string };
// bodyを持つエンドポイントだけ抽出
type EndpointWithBody = Extract<Endpoint, { body: unknown }>;
// nullやundefinedを除外する型
type NonNullableString = NonNullable<string | null | undefined>;
// string
ReturnType・Parameters・関数系ユーティリティ型
関数の型情報を抽出するユーティリティ型も、実務では非常に重要です。
ReturnType<T>:関数の戻り値型を取得する
ReturnType<T>は、関数型Tの戻り値の型を抽出します。サードパーティライブラリの関数から型を取得する場合に特に便利です。
function createUser(name: string, email: string) {
return {
id: Math.random(),
name,
email,
createdAt: new Date(),
};
}
// 関数の戻り値型を自動取得
type CreatedUser = ReturnType<typeof createUser>;
// { id: number; name: string; email: string; createdAt: Date }
// 非同期関数の場合
async function fetchUsers() {
const res = await fetch("/api/users");
return res.json() as Promise<User[]>;
}
// Awaited と組み合わせて Promise を解除
type FetchedUsers = Awaited<ReturnType<typeof fetchUsers>>;
// User[]
Parameters<T>:関数の引数型を取得する
Parameters<T>は、関数型Tの引数をタプル型として抽出します。
function sendNotification(
userId: number,
message: string,
options?: { urgent: boolean; channel: "email" | "slack" }
) {
// 通知送信処理
}
type NotificationParams = Parameters<typeof sendNotification>;
// [userId: number, message: string, options?: { urgent: boolean; channel: "email" | "slack" }]
// 特定の引数だけ取得
type NotificationOptions = Parameters<typeof sendNotification>[2];
// { urgent: boolean; channel: "email" | "slack" } | undefined
// ラッパー関数を型安全に作成
function logAndSend(...args: Parameters<typeof sendNotification>) {
console.log(`Sending notification to user ${args[0]}`);
return sendNotification(...args);
}
ConstructorParameters<T>:コンストラクタ引数を取得する
class ApiClient {
constructor(
private baseUrl: string,
private apiKey: string,
private timeout: number = 5000
) {}
}
type ApiClientArgs = ConstructorParameters<typeof ApiClient>;
// [baseUrl: string, apiKey: string, timeout?: number]
function createApiClient(...args: ConstructorParameters<typeof ApiClient>) {
return new ApiClient(...args);
}
実務で使えるユーティリティ型の組み合わせパターン
ユーティリティ型は単体でも便利ですが、組み合わせることでさらに強力な型を作れます。
CRUD操作の型定義パターン
interface Entity {
id: number;
createdAt: Date;
updatedAt: Date;
}
interface Article extends Entity {
title: string;
content: string;
author: string;
tags: string[];
status: "draft" | "published" | "archived";
}
// 作成時:id, createdAt, updatedAt は自動生成
type ArticleCreateInput = Omit<Article, keyof Entity>;
// 更新時:すべてオプショナル(ただしidは指定必須)
type ArticleUpdateInput = Pick<Article, "id"> & Partial<Omit<Article, "id" | "createdAt">>;
// 一覧表示用:軽量な型
type ArticleListItem = Pick<Article, "id" | "title" | "author" | "status" | "createdAt">;
// 検索フィルター用
type ArticleFilter = Partial<Pick<Article, "author" | "status" | "tags">> & {
keyword?: string;
dateFrom?: Date;
dateTo?: Date;
};
// リポジトリの型定義
interface ArticleRepository {
findAll(filter?: ArticleFilter): Promise<ArticleListItem[]>;
findById(id: number): Promise<Article | null>;
create(input: ArticleCreateInput): Promise<Article>;
update(input: ArticleUpdateInput): Promise<Article>;
delete(id: number): Promise<void>;
}
APIレスポンスの汎用型パターン
// 汎用APIレスポンス型
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
}
// ページネーション付きレスポンス
interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: {
page: number;
perPage: number;
total: number;
totalPages: number;
};
}
// エラーレスポンス
interface ApiError {
success: false;
error: {
code: string;
message: string;
details?: Record<string, string[]>;
};
}
// 成功・失敗を判別可能なユニオン型
type ApiResult<T> = ApiResponse<T> | ApiError;
// 使用例
async function fetchArticles(): Promise<PaginatedResponse<ArticleListItem>> {
const res = await fetch("/api/articles?page=1&perPage=20");
return res.json();
}
async function fetchArticle(id: number): Promise<ApiResult<Article>> {
const res = await fetch(`/api/articles/${id}`);
return res.json();
}
フォームバリデーションの型パターン
// フォームのエラー型を自動生成
type FormErrors<T> = Partial<Record<keyof T, string>>;
// フォーム状態の型
interface FormState<T> {
values: T;
errors: FormErrors<T>;
touched: Partial<Record<keyof T, boolean>>;
isSubmitting: boolean;
isValid: boolean;
}
// 使用例
type UserFormValues = Pick<User, "name" | "email" | "age" | "role">;
const formState: FormState<UserFormValues> = {
values: { name: "", email: "", age: 0, role: "viewer" },
errors: { email: "メールアドレスの形式が不正です" },
touched: { name: true, email: true },
isSubmitting: false,
isValid: false,
};
カスタムユーティリティ型の作り方
標準のユーティリティ型では対応できないケースでは、独自のユーティリティ型を定義できます。
DeepPartialの実装
標準のPartialはネストしたオブジェクトまではオプショナルにしません。再帰的にすべてをオプショナルにするDeepPartialを作ってみましょう。
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: DeepPartial<T[P]>
: T[P];
};
interface Company {
name: string;
address: {
prefecture: string;
city: string;
building?: string;
};
contact: {
phone: string;
email: string;
person: {
name: string;
department: string;
};
};
}
// ネストしたプロパティもすべてオプショナルに
type CompanyUpdate = DeepPartial<Company>;
const update: CompanyUpdate = {
contact: {
person: {
department: "新部署名",
},
},
};
特定キーだけをオプショナルにするPartialBy
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// age と role だけオプショナル、他は必須
type UserWithOptionalFields = PartialBy<User, "age" | "role">;
// 逆パターン:特定キーだけ必須にする
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
プロパティの型を変換するMapped Type
// すべてのプロパティをstring型に変換
type Stringify<T> = {
[P in keyof T]: string;
};
// フォームの初期値はすべて文字列として扱う
type UserFormStrings = Stringify<Pick<User, "name" | "email" | "age">>;
// { name: string; email: string; age: string }
// Nullable型:すべてのプロパティにnullを許容
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
type NullableUser = Nullable<User>;
// { id: number | null; name: string | null; ... }
// プレフィックス付きキーへの変換
type Prefixed<T, Prefix extends string> = {
[K in keyof T as `${Prefix}${Capitalize<string & K>}`]: T[K];
};
type ApiUser = Prefixed<Pick<User, "name" | "email">, "user">;
// { userName: string; userEmail: string }
ユーティリティ型を使う際のベストプラクティス
便利なユーティリティ型ですが、使いすぎると逆にコードの可読性が下がることがあります。
型の別名を適切に付ける
複雑な型操作の結果には、意味のある名前を付けましょう。
// 悪い例:何の型かわかりにくい
function createUser(input: Omit<User, "id" | "createdAt" | "updatedAt">): User {
// ...
}
// 良い例:型の意図が明確
type UserCreateInput = Omit<User, "id" | "createdAt" | "updatedAt">;
function createUser(input: UserCreateInput): User {
// ...
}
ネストを深くしすぎない
// 悪い例:読みにくく、エラーメッセージも複雑になる
type ComplexType = Partial<Pick<Omit<Required<User>, "id">, "name" | "email">>;
// 良い例:段階的に型を定義
type UserWithoutId = Omit<User, "id">;
type UserBasicInfo = Pick<UserWithoutId, "name" | "email">;
type OptionalBasicInfo = Partial<UserBasicInfo>;
型定義ファイルの整理
// types/user.ts - ドメインごとにファイルを分ける
export interface User {
id: number;
name: string;
email: string;
age: number;
role: Role;
createdAt: Date;
updatedAt: Date;
}
export type UserCreateInput = Omit<User, "id" | "createdAt" | "updatedAt">;
export type UserUpdateInput = Partial<UserCreateInput> & Pick<User, "id">;
export type UserListItem = Pick<User, "id" | "name" | "email" | "role">;
export type UserFilter = Partial<Pick<User, "role">> & { keyword?: string };
// types/common.ts - 汎用ユーティリティ型
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
export type Nullable<T> = { [P in keyof T]: T[P] | null };
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
まとめ
TypeScriptのユーティリティ型は、型定義の冗長さを解消し、安全で保守性の高いコードを書くための強力なツールです。
本記事で紹介した主要なユーティリティ型をまとめます。
プロパティ修飾系:Partial(全てオプショナル)、Required(全て必須)、Readonly(読み取り専用)
プロパティ選択系:Pick(選択)、Omit(除外)
マッピング系:Record(キーと値の型指定)
ユニオン操作系:Extract(抽出)、Exclude(除外)、NonNullable(null/undefined除外)
関数型系:ReturnType(戻り値型)、Parameters(引数型)、Awaited(Promise解除)
これらを適切に組み合わせることで、CRUDの入出力型、APIレスポンス型、フォーム型など、実務で必要な型を効率的に定義できます。まずはPartial・Pick・Omitの3つから実際のプロジェクトに取り入れてみてください。
関連記事
AIエージェント開発入門|自律型AIの仕組みと構築方法を解説【2026年版】
AI駆動コーディングワークフロー|Claude Code・Cursor・Copilotの実践的使い分け
プロンプトエンジニアリング上級編|Chain-of-Thought・Few-Shot・ReActの実践
APIレート制限の設計と実装|トークンバケット・スライディングウィンドウ解説
APIバージョニング戦略|URL・ヘッダー・クエリパラメータの使い分け
BIツール入門|Metabase・Redash・Looker Studioでデータ可視化する方法
チャットボット開発入門|LINE Bot・Slack Botの構築方法と活用事例
CI/CDパイプラインの基礎|継続的インテグレーション・デリバリーの全体像