TypeScriptのユーティリティ型完全ガイド|Partial・Pick・Omitなど実務で使う型操作

kento_morota 30分で読めます

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つから実際のプロジェクトに取り入れてみてください。

#TypeScript#ユーティリティ型
共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

起業準備に役立つ情報、もっとありますよ。

まずは話だけ聞いてもらう