TypeScript Utility Types完全リファレンス〜Partial・Pick・Omit・Record・実用25パターン【2026年版】〜

TypeScriptを書いていて「あれ、この型変換どうやるんだっけ?」と毎回ググっている方も多いのではないでしょうか。Utility Types(ユーティリティ型)はTypeScript標準で組み込まれた型変換ツールセットで、使いこなせば型定義の重複を一掃でき、コード量が体感3割減ります。

本記事はTS 5.x準拠の実用リファレンスとして、25種類以上のUtility Typeをコピペで動くコード付きで解説します。標準型22種+自作の便利型8種+実戦パターン(React Props・APIレスポンス・Form状態)まで、現役Webエンジニアが日々使っているものを総ざらいします。

この記事で得られること

  • 標準Utility Type 22種類の動作仕様と典型ユースケース
  • コピペで即動く30個以上のコードサンプル(TS 5.x対応)
  • DeepPartial / DeepReadonly / Brand型など自作型の実装
  • React Props・APIレスポンス・Form状態への適用パターン
  1. Utility Typesとは何か
    1. なぜUtility Typesが必要なのか
    2. 本記事で扱う型カテゴリ一覧
  2. プロパティ変換系 Utility Types
    1. Partial<T> – 全プロパティをoptional化
    2. Required<T> – 全プロパティを必須化
    3. Readonly<T> – 全プロパティをreadonly化
  3. キー選択系 Utility Types
    1. Pick<T, K> – 指定キーだけを抽出
    2. Omit<T, K> – 指定キーを除外
    3. PickとOmitの使い分け
  4. マップ生成系 Utility Types
    1. Record<K, T> – 辞書型を作る
  5. ユニオン操作系 Utility Types
    1. Exclude<T, U> – ユニオンから除外
    2. Extract<T, U> – 一致するメンバーだけ抽出
    3. NonNullable<T> – nullとundefinedを除去
  6. 関数型抽出系 Utility Types
    1. ReturnType<T> – 関数の戻り値型
    2. Parameters<T> – 関数の引数型タプル
    3. ConstructorParameters<T> – コンストラクタの引数型
    4. InstanceType<T> – クラスのインスタンス型
    5. ThisParameterType / OmitThisParameter
    6. ThisType<T> – メソッド内thisの型指定
  7. Promise/非同期系 Utility Types
    1. Awaited<T> – Promise解決後の型
    2. NoInfer<T> – 型推論を抑制(TS 5.4+)
  8. 文字列リテラル変換系 Utility Types
    1. Capitalize<S> – 先頭を大文字に
    2. Uncapitalize<S> – 先頭を小文字に
    3. Uppercase / Lowercase
  9. 自作Utility Types(超実用)
    1. DeepPartial<T> – ネストしたobjectも全てoptional化
    2. DeepReadonly<T> – ネストまでreadonly
    3. Mutable<T> – readonlyを剥がす
    4. ValueOf<T> – オブジェクト型の値ユニオン
    5. PartialBy<T, K> – 指定キーだけoptional
    6. RequireBy<T, K> – 指定キーだけ必須
    7. Brand型 – 公称型を実現
    8. Result<T, E> – エラーハンドリング型
  10. 実戦パターン: React + APIで使い倒す
    1. React Props型の派生パターン
    2. APIレスポンス→クライアント型への変換
    3. Form状態の進化を表現する
    4. Reduxアクションの型生成
  11. FAQ – よくある質問
    1. Q1. PickとOmitはどう使い分ければいいですか?
    2. Q2. PartialとDeepPartialの違いは?
    3. Q3. ExcludeとOmitの違いは?
    4. Q4. ReturnTypeはasync関数でPromiseになってしまいます。中身の型が欲しい時は?
    5. Q5. Readonlyは本当に書き換えを防げる?
    6. Q6. 自作型と標準型、どちらを優先すべき?
    7. Q7. Utility Typesを使いすぎると型が読めなくなりませんか?
  12. 体系的に学ぶならスクール活用も選択肢
  13. まとめ – Utility Types使い分けマップ

Utility Typesとは何か

Utility Typesは、既存の型を変換して新しい型を生成するためのTypeScript組み込みの型関数群です。lib.es5.d.tsに定義されており、追加のimportは不要で全プロジェクトで即利用できます。

なぜUtility Typesが必要なのか

同じプロパティを持つ型を毎回手書きしていると、変更時に全箇所を修正する必要が出てきます。下の「悪い例」を見てください。

// ❌ 悪い例: 同じプロパティを2回書いている
type User = {
  id: string;
  name: string;
  email: string;
};

type UserDraft = {
  id?: string;
  name?: string;
  email?: string;
};

これをPartial<T>で書き換えると、Userの定義変更が自動的にUserDraftに反映されます。

// ✅ 良い例: Utility Typeで派生
type User = {
  id: string;
  name: string;
  email: string;
};

type UserDraft = Partial<User>; // 全プロパティが自動でoptional化

本記事で扱う型カテゴリ一覧

カテゴリ 代表的な型 主用途
プロパティ変換 Partial / Required / Readonly optional/必須/readonly切替
キー選択 Pick / Omit プロパティの抽出/除外
マップ生成 Record 辞書型の生成
ユニオン操作 Exclude / Extract / NonNullable ユニオン型の絞り込み
関数型抽出 ReturnType / Parameters 関数の戻り値/引数型
クラス型抽出 ConstructorParameters / InstanceType クラスの引数/インスタンス型
Promise/非同期 Awaited Promise解決後の型
文字列リテラル Capitalize / Uppercase 等 文字列リテラル型変換

プロパティ変換系 Utility Types

Partial<T> – 全プロパティをoptional化

使用頻度: ★★★★★。Utility Typesの中で最もよく使われる型です。フォームの中間状態や、PATCHリクエスト(部分更新)で大活躍します。

// 定義(イメージ)
type Partial<T> = { [P in keyof T]?: T[P] };

type User = {
  id: string;
  name: string;
  email: string;
  age: number;
};

type UserUpdate = Partial<User>;
// 型: { id?: string; name?: string; email?: string; age?: number }

// 実用例: PATCHエンドポイント
async function updateUser(id: string, patch: Partial<User>) {
  return fetch(`/api/users/${id}`, {
    method: "PATCH",
    body: JSON.stringify(patch),
  });
}

// 一部だけ指定して呼び出せる
await updateUser("u1", { email: "new@example.com" });

Required<T> – 全プロパティを必須化

使用頻度: ★★★☆☆。Partialと逆の操作です。optionalな型を強制的に全フィールド必須に変えます。

type Config = {
  host?: string;
  port?: number;
  timeout?: number;
};

type ResolvedConfig = Required<Config>;
// 型: { host: string; port: number; timeout: number }

// 実用例: デフォルト値マージ後の戻り値型
function resolveConfig(config: Config): Required<Config> {
  return {
    host: config.host ?? "localhost",
    port: config.port ?? 3000,
    timeout: config.timeout ?? 5000,
  };
}

const resolved = resolveConfig({ host: "example.com" });
// resolved.port は number (undefinedにならない)

Readonly<T> – 全プロパティをreadonly化

使用頻度: ★★★★☆。Reduxのstateや関数引数の不変保証に使います。コンパイル時のみの保証で、ランタイムでは変更可能な点に注意してください。

type Point = { x: number; y: number };
type FrozenPoint = Readonly<Point>;
// 型: { readonly x: number; readonly y: number }

const p: FrozenPoint = { x: 1, y: 2 };
// p.x = 3; // ❌ Error: Cannot assign to 'x' because it is a read-only property

// 実用例: 関数引数を変更不可にする
function distance(a: Readonly<Point>, b: Readonly<Point>): number {
  // a.x = 0; // ❌ コンパイルエラー
  return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}

キー選択系 Utility Types

Pick<T, K> – 指定キーだけを抽出

使用頻度: ★★★★★。大きな型から「一覧表示用に必要な3項目だけ」など、サブセット型を作るのに使います。

type Article = {
  id: string;
  title: string;
  body: string;
  author: string;
  publishedAt: Date;
  views: number;
};

// 一覧表示用にid/title/authorだけ抜き出す
type ArticleSummary = Pick<Article, "id" | "title" | "author">;
// 型: { id: string; title: string; author: string }

// 実用例: 一覧APIのレスポンス型
async function fetchArticleList(): Promise<ArticleSummary[]> {
  const res = await fetch("/api/articles");
  return res.json();
}

Omit<T, K> – 指定キーを除外

使用頻度: ★★★★★。Pickの逆で「サーバー側が生成するidだけ除く」というように、特定フィールドを抜いた型を作ります。

type Article = {
  id: string;
  title: string;
  body: string;
  author: string;
  publishedAt: Date;
  views: number;
};

// id・publishedAt・viewsはサーバー側が生成
type ArticleDraft = Omit<Article, "id" | "publishedAt" | "views">;
// 型: { title: string; body: string; author: string }

// 実用例: 投稿フォームの送信ペイロード
async function createArticle(draft: ArticleDraft): Promise<Article> {
  const res = await fetch("/api/articles", {
    method: "POST",
    body: JSON.stringify(draft),
  });
  return res.json();
}

PickとOmitの使い分け

選び方の指針
・抽出するキーが少数(全体の半数以下)なら Pick
・除外するキーが少数(1〜3個)なら Omit
・型情報の意図が「これだけ欲しい」ならPick、「あれだけ要らない」ならOmit

マップ生成系 Utility Types

Record<K, T> – 辞書型を作る

使用頻度: ★★★★★。キーと値の型を指定して辞書型(マップ型)を作ります。文字列リテラルユニオンをキーに使うのが鉄板パターンです。

// 基本: 全キーがstringの辞書
type StringMap = Record<string, string>;
const headers: StringMap = {
  "Content-Type": "application/json",
  "Authorization": "Bearer xxx",
};

// 強力: リテラルユニオンをキーに
type Status = "pending" | "success" | "error";
type StatusMessage = Record<Status, string>;

const messages: StatusMessage = {
  pending: "処理中です",
  success: "完了しました",
  error: "エラーが発生しました",
  // どれか抜けるとコンパイルエラーになる(型安全な辞書)
};
// 実用例: 国別の通貨表示
type Country = "JP" | "US" | "GB" | "DE";

type CurrencyInfo = {
  code: string;
  symbol: string;
  decimals: number;
};

const currencies: Record<Country, CurrencyInfo> = {
  JP: { code: "JPY", symbol: "¥", decimals: 0 },
  US: { code: "USD", symbol: "$", decimals: 2 },
  GB: { code: "GBP", symbol: "£", decimals: 2 },
  DE: { code: "EUR", symbol: "€", decimals: 2 },
};

function format(country: Country, amount: number): string {
  const c = currencies[country];
  return `${c.symbol}${amount.toFixed(c.decimals)}`;
}

ユニオン操作系 Utility Types

Exclude<T, U> – ユニオンから除外

使用頻度: ★★★★☆。ユニオン型から特定のメンバーを取り除きます。Omitがオブジェクト型用、Excludeはユニオン型用と覚えてください。

type EventName = "click" | "hover" | "focus" | "blur" | "submit";

// submit以外のイベント
type MouseEventName = Exclude<EventName, "submit">;
// 型: "click" | "hover" | "focus" | "blur"

// 複数除外も可能
type NonFormEvent = Exclude<EventName, "submit" | "focus" | "blur">;
// 型: "click" | "hover"

// プリミティブ型での除外
type Primitive = string | number | boolean | null | undefined;
type NonNull = Exclude<Primitive, null | undefined>;
// 型: string | number | boolean

Extract<T, U> – 一致するメンバーだけ抽出

使用頻度: ★★★☆☆。Excludeの逆で、ユニオン型から指定型に「割り当て可能なメンバーだけ」を取り出します。

type EventName = "click" | "hover" | "focus" | "blur" | "submit";

// マウス系のイベントだけ抽出
type MouseEventName = Extract<EventName, "click" | "hover">;
// 型: "click" | "hover"

// 実用例: タグ付きユニオンから特定タグだけ抽出
type Action =
  | { type: "LOAD_START" }
  | { type: "LOAD_SUCCESS"; payload: string[] }
  | { type: "LOAD_ERROR"; error: Error };

type SuccessAction = Extract<Action, { type: "LOAD_SUCCESS" }>;
// 型: { type: "LOAD_SUCCESS"; payload: string[] }

function handleSuccess(action: SuccessAction) {
  console.log(action.payload); // 型安全にpayloadへアクセス
}

NonNullable<T> – nullとundefinedを除去

使用頻度: ★★★★☆Exclude<T, null | undefined>の専用シンタックスです。短く書けて意図も明確です。

type MaybeUser = User | null | undefined;
type DefinitelyUser = NonNullable<MaybeUser>;
// 型: User

// 実用例: 配列のフィルタ後の型
const items: (string | null | undefined)[] = ["a", null, "b", undefined, "c"];

const filtered: string[] = items.filter(
  (x): x is NonNullable<typeof x> => x != null
);
// filtered の型は string[]

関数型抽出系 Utility Types

ReturnType<T> – 関数の戻り値型

使用頻度: ★★★★★。既存関数の戻り値型を再利用するために頻繁に使われます。typeofと組み合わせるのが王道です。

function fetchUser(id: string) {
  return {
    id,
    name: "Alice",
    email: "alice@example.com",
    createdAt: new Date(),
  };
}

type User = ReturnType<typeof fetchUser>;
// 型: { id: string; name: string; email: string; createdAt: Date }

// async関数の場合はPromiseが返るのでAwaitedと組み合わせる
async function fetchUserAsync(id: string) {
  return { id, name: "Alice", email: "alice@example.com" };
}

type UserAsync = Awaited<ReturnType<typeof fetchUserAsync>>;
// 型: { id: string; name: string; email: string }

Parameters<T> – 関数の引数型タプル

使用頻度: ★★★★☆。関数の引数型を取り出して再利用します。ラッパー関数を作るときに便利です。

function createUser(name: string, age: number, role: "admin" | "user") {
  return { name, age, role };
}

type CreateUserArgs = Parameters<typeof createUser>;
// 型: [name: string, age: number, role: "admin" | "user"]

// 実用例: ログ記録付きラッパー
function withLog<T extends (...args: any[]) => any>(fn: T) {
  return (...args: Parameters<T>): ReturnType<T> => {
    console.log("calling with", args);
    return fn(...args);
  };
}

const loggedCreateUser = withLog(createUser);
loggedCreateUser("Bob", 30, "admin"); // 型安全

ConstructorParameters<T> – コンストラクタの引数型

使用頻度: ★★☆☆☆。クラスのコンストラクタ引数を取り出します。クラスをラップしたり、ファクトリ関数を作る場面で使います。

class HttpClient {
  constructor(
    public baseUrl: string,
    public timeout: number,
    public retries: number = 3
  ) {}
}

type HttpClientArgs = ConstructorParameters<typeof HttpClient>;
// 型: [baseUrl: string, timeout: number, retries?: number]

// 実用例: ファクトリ関数
function createHttpClient(...args: ConstructorParameters<typeof HttpClient>) {
  return new HttpClient(...args);
}

const client = createHttpClient("https://api.example.com", 5000);

InstanceType<T> – クラスのインスタンス型

使用頻度: ★★☆☆☆。クラスの型から生成されるインスタンスの型を取り出します。typeof MyClassと組み合わせるのが定番です。

class EventEmitter {
  private listeners: Map<string, Function[]> = new Map();

  on(event: string, fn: Function) {
    /* ... */
  }
  emit(event: string, ...args: any[]) {
    /* ... */
  }
}

type EventEmitterInstance = InstanceType<typeof EventEmitter>;
// 型: EventEmitter

// 実用例: DIコンテナでクラスを登録
const registry = new Map<string, InstanceType<typeof EventEmitter>>();
registry.set("default", new EventEmitter());

ThisParameterType / OmitThisParameter

使用頻度: ★☆☆☆☆。明示的にthisパラメータを宣言した関数で、その型を抽出/除去します。古いjQuery系コードのラッピングで使うことがあります。

function describe(this: { name: string }, prefix: string): string {
  return `${prefix}: ${this.name}`;
}

type DescribeThis = ThisParameterType<typeof describe>;
// 型: { name: string }

type DescribeNoThis = OmitThisParameter<typeof describe>;
// 型: (prefix: string) => string

// bind後の型を表現するのに便利
const bound: DescribeNoThis = describe.bind({ name: "Alice" });
console.log(bound("Hello")); // "Hello: Alice"

ThisType<T> – メソッド内thisの型指定

使用頻度: ★☆☆☆☆。オブジェクトリテラル内のメソッドでthisの型を統一する特殊なユーティリティです。noImplicitThis有効時に効果を発揮します。

type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>; // メソッド内のthisが D & M になる
};

function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
  return { ...desc.data, ...desc.methods } as D & M;
}

const obj = makeObject({
  data: { x: 0, y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx; // thisが{x:number;y:number;moveBy:...}として推論
      this.y += dy;
    },
  },
});

Promise/非同期系 Utility Types

Awaited<T> – Promise解決後の型

使用頻度: ★★★★☆。TypeScript 4.5から導入された強力な型で、ネストされたPromiseも再帰的に展開してくれます。

type A = Awaited<Promise<string>>;
// 型: string

type B = Awaited<Promise<Promise<number>>>;
// 型: number (再帰的に展開)

// 実用例: async関数の戻り値型を取得
async function fetchPosts() {
  const res = await fetch("/api/posts");
  return res.json() as Promise<{ id: string; title: string }[]>;
}

type Posts = Awaited<ReturnType<typeof fetchPosts>>;
// 型: { id: string; title: string }[]

// Promise.allの結果型を取得
async function fetchAll() {
  return Promise.all([
    fetch("/a").then((r) => r.text()),
    fetch("/b").then((r) => r.json() as Promise<number>),
  ]);
}

type AllResult = Awaited<ReturnType<typeof fetchAll>>;
// 型: [string, number]

NoInfer<T> – 型推論を抑制(TS 5.4+)

使用頻度: ★★☆☆☆。TypeScript 5.4から追加された新型です。ジェネリック関数で「特定の引数では推論させたくない」シーンに使います。

// 5.4以前: 第二引数の型がT推論に巻き込まれて意図しない型になる
function createSet<T>(initial: T[], defaultValue: T): T[] {
  return [...initial, defaultValue];
}
// createSet([1, 2, 3], "x") // T が number | string になってしまう

// 5.4+ NoInferを使うと第一引数からだけTが推論される
function createSetSafe<T>(initial: T[], defaultValue: NoInfer<T>): T[] {
  return [...initial, defaultValue];
}
// createSetSafe([1, 2, 3], "x") // ❌ Error: string is not assignable to number

文字列リテラル変換系 Utility Types

Capitalize<S> – 先頭を大文字に

type A = Capitalize<"hello">;
// 型: "Hello"

// 実用例: イベント名からハンドラ名を生成
type EventName = "click" | "change" | "submit";
type HandlerName<E extends string> = `on${Capitalize<E>}`;

type Handlers = HandlerName<EventName>;
// 型: "onClick" | "onChange" | "onSubmit"

Uncapitalize<S> – 先頭を小文字に

type B = Uncapitalize<"Hello">;
// 型: "hello"

// 実用例: APIレスポンスのキャメルケース変換型
type ApiKey = "UserId" | "UserName" | "EmailAddress";
type JsKey = Uncapitalize<ApiKey>;
// 型: "userId" | "userName" | "emailAddress"

Uppercase / Lowercase

type Upper = Uppercase<"hello world">;
// 型: "HELLO WORLD"

type Lower = Lowercase<"HELLO WORLD">;
// 型: "hello world"

// 実用例: 環境変数名の生成
type Env = "development" | "production" | "staging";
type EnvVar = `APP_${Uppercase<Env>}_URL`;
// 型: "APP_DEVELOPMENT_URL" | "APP_PRODUCTION_URL" | "APP_STAGING_URL"

自作Utility Types(超実用)

標準にはないけれど実務でほぼ必須になる自作型を紹介します。そのままコピペしてutils/types.tsに入れておくとあらゆるプロジェクトで再利用できます

DeepPartial<T> – ネストしたobjectも全てoptional化

type DeepPartial<T> = T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T;

type Settings = {
  theme: {
    color: string;
    fontSize: number;
  };
  notifications: {
    email: boolean;
    push: {
      enabled: boolean;
      sound: string;
    };
  };
};

type SettingsPatch = DeepPartial<Settings>;

const patch: SettingsPatch = {
  theme: { color: "dark" }, // fontSize不要
  notifications: { push: { enabled: false } }, // emailもsoundも不要
};

DeepReadonly<T> – ネストまでreadonly

type DeepReadonly<T> = T extends (infer U)[]
  ? ReadonlyArray<DeepReadonly<U>>
  : T extends object
  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  : T;

type State = {
  user: { name: string; tags: string[] };
  items: { id: string; qty: number }[];
};

type FrozenState = DeepReadonly<State>;

const s: FrozenState = {
  user: { name: "Alice", tags: ["admin"] },
  items: [{ id: "1", qty: 10 }],
};

// s.user.name = "Bob"; // ❌ readonly
// s.user.tags.push("x"); // ❌ ReadonlyArrayはpush不可
// s.items[0].qty = 5; // ❌ readonly

Mutable<T> – readonlyを剥がす

type Mutable<T> = { -readonly [K in keyof T]: T[K] };

type Frozen = Readonly<{ x: number; y: number }>;
type Movable = Mutable<Frozen>;
// 型: { x: number; y: number }

const m: Movable = { x: 0, y: 0 };
m.x = 10; // ✅

ValueOf<T> – オブジェクト型の値ユニオン

type ValueOf<T> = T[keyof T];

const ROLES = {
  ADMIN: "admin",
  USER: "user",
  GUEST: "guest",
} as const;

type Role = ValueOf<typeof ROLES>;
// 型: "admin" | "user" | "guest"

function checkRole(role: Role) {
  /* ... */
}
checkRole(ROLES.ADMIN);

PartialBy<T, K> – 指定キーだけoptional

type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

type User = {
  id: string;
  name: string;
  email: string;
};

// idだけoptional(サーバーで自動生成のため)
type UserInput = PartialBy<User, "id">;
// 型: { name: string; email: string; id?: string }

RequireBy<T, K> – 指定キーだけ必須

type RequireBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

type Profile = {
  name?: string;
  age?: number;
  bio?: string;
};

// nameだけ必須
type RegistrableProfile = RequireBy<Profile, "name">;
// 型: { age?: number; bio?: string; name: string }

Brand型 – 公称型を実現

TypeScriptは構造的型付けなのでstring同士は区別されません。Brand型で「UserId」と「PostId」のような同じstringでも混同を防げます。

type Brand<T, B> = T & { readonly __brand: B };

type UserId = Brand<string, "UserId">;
type PostId = Brand<string, "PostId">;

function asUserId(id: string): UserId {
  return id as UserId;
}
function asPostId(id: string): PostId {
  return id as PostId;
}

function getUser(id: UserId) {
  /* ... */
}

const uid = asUserId("u-001");
const pid = asPostId("p-001");

getUser(uid); // ✅
// getUser(pid); // ❌ PostIdはUserIdに代入不可
// getUser("u-001"); // ❌ 生のstringも不可

Result<T, E> – エラーハンドリング型

type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };

async function safeFetch<T>(url: string): Promise<Result<T>> {
  try {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return { ok: true, value: (await res.json()) as T };
  } catch (e) {
    return { ok: false, error: e as Error };
  }
}

// 呼び出し側はResult型でハンドリング
const r = await safeFetch<{ id: string }>("/api/user");
if (r.ok) {
  console.log(r.value.id);
} else {
  console.error(r.error.message);
}

実戦パターン: React + APIで使い倒す

React Props型の派生パターン

import { ComponentProps, ButtonHTMLAttributes } from "react";

// 既存コンポーネントのPropsを継承して拡張
type ButtonProps = ComponentProps<"button"> & {
  variant?: "primary" | "secondary";
  loading?: boolean;
};

// HTMLAttributesから一部除外して独自プロパティ追加
type CardProps = Omit<ButtonHTMLAttributes<HTMLDivElement>, "onClick"> & {
  onCardClick: (id: string) => void;
  cardId: string;
};

// Propsの一部だけoptional化
type ModalProps = {
  isOpen: boolean;
  title: string;
  onClose: () => void;
  size: "sm" | "md" | "lg";
};

// 「sizeを省略可能なModal」を派生
type SimpleModalProps = PartialBy<ModalProps, "size">;

APIレスポンス→クライアント型への変換

// APIから返ってくる生の型(snake_case + nullable多め)
type ApiUser = {
  id: number;
  user_name: string;
  email_address: string | null;
  created_at: string;
  updated_at: string | null;
};

// クライアントで使う型: camelCase + 必須化 + Date型化
type User = {
  id: number;
  userName: string;
  emailAddress: NonNullable<ApiUser["email_address"]>;
  createdAt: Date;
  updatedAt: Date | null;
};

function transformUser(raw: ApiUser): User {
  return {
    id: raw.id,
    userName: raw.user_name,
    emailAddress: raw.email_address ?? "",
    createdAt: new Date(raw.created_at),
    updatedAt: raw.updated_at ? new Date(raw.updated_at) : null,
  };
}

Form状態の進化を表現する

// 元となる完全な型
type SignupForm = {
  email: string;
  password: string;
  passwordConfirm: string;
  agreeToTerms: boolean;
};

// ① 入力中(全てoptional)
type FormDraft = Partial<SignupForm>;

// ② エラー状態(各フィールドのエラーメッセージ)
type FormErrors = Partial<Record<keyof SignupForm, string>>;

// ③ touched状態(各フィールドが触られたか)
type FormTouched = Partial<Record<keyof SignupForm, boolean>>;

// ④ 送信用ペイロード(passwordConfirm除外)
type SignupPayload = Omit<SignupForm, "passwordConfirm">;

// useReducerでまとめて管理する例
type FormState = {
  values: FormDraft;
  errors: FormErrors;
  touched: FormTouched;
  isSubmitting: boolean;
};

Reduxアクションの型生成

// すべてのactionハンドラのマップ
const handlers = {
  USER_LOAD: (state: State, payload: { id: string }) => state,
  USER_SUCCESS: (state: State, payload: { user: User }) => state,
  USER_ERROR: (state: State, payload: { message: string }) => state,
};

// 各ハンドラの第二引数からpayload型を抽出
type ActionMap = {
  [K in keyof typeof handlers]: {
    type: K;
    payload: Parameters<(typeof handlers)[K]>[1];
  };
};

// ユニオン化
type Action = ActionMap[keyof ActionMap];
// 型: { type:"USER_LOAD"; payload:{id:string} } | { type:"USER_SUCCESS"; payload:{user:User} } | ...

function reducer(state: State, action: Action): State {
  return handlers[action.type](state, action.payload as never);
}

FAQ – よくある質問

Q1. PickとOmitはどう使い分ければいいですか?

抽出するキーが少ない(全体の半数以下)ならPick、除外するキーが1〜3個ならOmitが読みやすいです。例えば10フィールド中3つだけ欲しければPick、10フィールド中1つだけ消したければOmit、と使い分けてください。

Q2. PartialとDeepPartialの違いは?

Partialはトップレベルのプロパティだけoptional化します。ネストしたobjectの内部は変化しません。DeepPartial(自作)は再帰的に全階層をoptional化します。設定オブジェクトのマージなど階層構造を扱うときはDeepPartialを使ってください。

Q3. ExcludeとOmitの違いは?

Exclude<T, U>はユニオン型用、Omit<T, K>はオブジェクト型用です。Exclude<"a"|"b"|"c", "a">"b"|"c"を返し、Omit<{a:1;b:2}, "a">{b:2}を返します。

Q4. ReturnTypeはasync関数でPromiseになってしまいます。中身の型が欲しい時は?

Awaited<ReturnType<typeof fn>>と組み合わせてください。TypeScript 4.5以降はAwaitedがネストPromiseも再帰展開してくれるので、これ一つで解決します。

Q5. Readonlyは本当に書き換えを防げる?

コンパイル時のみの保証です。readonlyはTypeScriptの型情報なので、JavaScriptランタイムでは普通に書き換えられます。実行時に不変にしたい場合はObject.freeze()を併用してください。

Q6. 自作型と標準型、どちらを優先すべき?

標準で済むなら標準を優先してください。標準型は他の開発者にも知られているので可読性が高いです。標準にないDeepPartialBrand型などは自作してutils/types.tsに集約しておくとプロジェクト全体で再利用しやすくなります。

Q7. Utility Typesを使いすぎると型が読めなくなりませんか?

過剰な合成は確かに読みにくくなります。3段以上ネスト(Partial<Pick<Omit<T, "x">, "y">>など)になったら、中間型をtype Intermediate = ...で名前付けすることをおすすめします。VS Codeのホバーで型展開が見えるかどうかを基準にしてください。

体系的に学ぶならスクール活用も選択肢

Utility Typesを使いこなすには「型システム全体への理解」「ジェネリクスとの組み合わせ」「実プロジェクトでの経験」が欠かせません。独学で詰まったときは、現役エンジニアからのレビューを受けられる環境を整えると一気に成長します。

TypeScript実務スキルを伸ばすためのスクール

  • テックアカデミー: 現役エンジニアのメンタリングで型設計をレビューしてもらえる
  • 侍エンジニア: マンツーマンでオーダーメイドカリキュラム。TS特化学習も相談可能
  • DMM WEBCAMP: チーム開発でフロントエンドを経験。TSの実プロジェクト経験を積める
  • レバテック: TS案件に強い転職エージェント。スキル棚卸しからキャリア相談まで

まとめ – Utility Types使い分けマップ

やりたいこと 使う型
PATCH用に全フィールドoptional Partial<T>
デフォルト値マージ後の確定型 Required<T>
変更不可なstate型 Readonly<T> / DeepReadonly<T>
一覧表示用にプロパティを絞る Pick<T, K>
サーバー生成フィールドを除く Omit<T, K>
型安全な辞書/設定マップ Record<K, T>
ユニオン型からメンバー除外 Exclude<T, U>
タグ付きユニオンから特定タグ抽出 Extract<T, U>
null/undefinedを除去 NonNullable<T>
関数の戻り値型を流用 ReturnType<typeof fn>
async関数の解決型を取得 Awaited<ReturnType<typeof fn>>
同じstring同士の混同を防ぐ Brand<T, B>(自作)
try/catchの代替エラー型 Result<T, E>(自作)

Utility Typesは「型を書く時間を減らし、変更に強いコードを生む」TypeScript最大の武器です。まずはPartial / Pick / Omit / Recordの4つを意識的に使うところから始めてみてください。慣れてきたらReturnType・Awaited・自作のBrand型/Result型へと広げていけば、型表現力が飛躍的に伸びます。

本記事は今後も最新TSバージョン(NoInferのようなTS 5.4新型を含む)に追従して更新していきます。ブックマークしてリファレンスとして使ってください。

コメント

タイトルとURLをコピーしました