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状態への適用パターン
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. 自作型と標準型、どちらを優先すべき?
標準で済むなら標準を優先してください。標準型は他の開発者にも知られているので可読性が高いです。標準にないDeepPartialやBrand型などは自作してutils/types.tsに集約しておくとプロジェクト全体で再利用しやすくなります。
Q7. Utility Typesを使いすぎると型が読めなくなりませんか?
過剰な合成は確かに読みにくくなります。3段以上ネスト(Partial<Pick<Omit<T, "x">, "y">>など)になったら、中間型をtype Intermediate = ...で名前付けすることをおすすめします。VS Codeのホバーで型展開が見えるかどうかを基準にしてください。
体系的に学ぶならスクール活用も選択肢
Utility Typesを使いこなすには「型システム全体への理解」「ジェネリクスとの組み合わせ」「実プロジェクトでの経験」が欠かせません。独学で詰まったときは、現役エンジニアからのレビューを受けられる環境を整えると一気に成長します。
- テックアカデミー: 現役エンジニアのメンタリングで型設計をレビューしてもらえる
- 侍エンジニア: マンツーマンでオーダーメイドカリキュラム。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新型を含む)に追従して更新していきます。ブックマークしてリファレンスとして使ってください。

コメント