TypeScriptには、既存の型を変換して新しい型を作るユーティリティ型(Utility Types)が標準で用意されています。Partial、Pick、Omit、Record、Readonly など全20種以上あり、これらを使いこなすことで型定義の重複を排除し、保守性と安全性の高い型設計が実現できます。
「型をいちいち手書きするのが面倒」「似たような型を何度も定義してしまう」「APIレスポンスの一部だけ使いたいのに型が合わない」――こうした課題を、ユーティリティ型は宣言的かつ型安全に解決します。
この記事では、TypeScript が提供するユーティリティ型を全種類取り上げ、それぞれ基本構文・内部実装・実務ユースケースの3軸で徹底解説します。さらに、複数のユーティリティ型を組み合わせる実践パターンや、標準にはないカスタムユーティリティ型の作り方まで網羅します。
この記事で学べること
- ユーティリティ型とは何か ― なぜ必要で、どう便利なのか
- Partial / Required / Readonly ― プロパティの修飾を変換する型
- Record ― キーと値の型を指定してオブジェクト型を作る
- Pick / Omit ― オブジェクト型からプロパティを選択・除外する
- Exclude / Extract ― Union型から特定のメンバーを除外・抽出する
- NonNullable ― null と undefined を除外する
- ReturnType / Parameters ― 関数の戻り値型・引数型を取得する
- ConstructorParameters / InstanceType ― クラスのコンストラクタ型・インスタンス型を取得する
- Awaited ― Promiseのネストをアンラップする
- Uppercase / Lowercase / Capitalize / Uncapitalize ― 文字列リテラル型を操作する
- ThisParameterType / OmitThisParameter / ThisType ― this の型を操作する
- 複数のユーティリティ型を組み合わせる実務パターン集
- DeepPartial / DeepReadonly / Mutable など自作ユーティリティ型の作り方
- よくあるエラーと対処法
ユーティリティ型とは ― なぜ必要なのか
ユーティリティ型(Utility Types)とは、TypeScriptが標準ライブラリ(lib.es5.d.ts 等)で提供している型変換のための組み込みジェネリック型です。既存の型を入力として受け取り、プロパティの追加・削除・変換などを行った新しい型を返します。
型の「DRY原則」を実現する
プログラムのコードに「同じことを二度書くな(DRY: Don’t Repeat Yourself)」という原則があるように、型定義にも同じ原則が当てはまります。ユーティリティ型を使えば、元となる型から派生型を宣言的に生成でき、型定義の重複や不整合を防げます。
ユーティリティ型を使わない場合(型の重複)
// 元の型
interface User {
id: number;
name: string;
email: string;
age: number;
}
// 更新用の型を手書きで定義 → User と二重管理になる
interface UserUpdate {
name?: string;
email?: string;
age?: number;
}
// プロフィール表示用の型も手書き → さらに重複
interface UserProfile {
name: string;
email: string;
}
// User に createdAt を追加したら、UserUpdate と UserProfile も手動で修正が必要...
ユーティリティ型を使った場合(型の再利用)
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial + Omit で更新用の型を派生
type UserUpdate = Partial<Omit<User, 'id'>>;
// Pick でプロフィール表示用の型を派生
type UserProfile = Pick<User, 'name' | 'email'>;
// User に createdAt を追加しても、UserUpdate は自動的に追従する
ポイント:ユーティリティ型を使えば、元の型(Single Source of Truth)を変更するだけで、派生型が自動的に追従します。これが型レベルの DRY 原則です。
ユーティリティ型 全一覧
TypeScript が標準で提供するユーティリティ型の全一覧です。この記事ではすべてを詳しく解説します。
| ユーティリティ型 |
カテゴリ |
説明 |
Partial<T> |
プロパティ修飾 |
全プロパティをオプショナル(?)に変換 |
Required<T> |
プロパティ修飾 |
全プロパティを必須に変換 |
Readonly<T> |
プロパティ修飾 |
全プロパティを読み取り専用に変換 |
Record<K, T> |
オブジェクト構築 |
キー K と値 T からオブジェクト型を構築 |
Pick<T, K> |
プロパティ選択 |
T から指定プロパティだけを抽出 |
Omit<T, K> |
プロパティ選択 |
T から指定プロパティを除外 |
Exclude<T, U> |
Union操作 |
Union型 T から U に代入可能な型を除外 |
Extract<T, U> |
Union操作 |
Union型 T から U に代入可能な型だけを抽出 |
NonNullable<T> |
Union操作 |
T から null と undefined を除外 |
ReturnType<T> |
関数型操作 |
関数型 T の戻り値型を取得 |
Parameters<T> |
関数型操作 |
関数型 T の引数型をタプルで取得 |
ConstructorParameters<T> |
クラス型操作 |
コンストラクタの引数型をタプルで取得 |
InstanceType<T> |
クラス型操作 |
コンストラクタ関数のインスタンス型を取得 |
Awaited<T> |
Promise操作 |
Promise の resolve 値の型を再帰的に取得 |
Uppercase<S> |
文字列操作 |
文字列リテラル型を大文字に変換 |
Lowercase<S> |
文字列操作 |
文字列リテラル型を小文字に変換 |
Capitalize<S> |
文字列操作 |
文字列リテラル型の先頭を大文字に変換 |
Uncapitalize<S> |
文字列操作 |
文字列リテラル型の先頭を小文字に変換 |
ThisParameterType<T> |
this操作 |
関数型の this パラメータ型を取得 |
OmitThisParameter<T> |
this操作 |
関数型から this パラメータを除去 |
ThisType<T> |
this操作 |
オブジェクトリテラル内の this の型を指定 |
それでは、各ユーティリティ型を詳しく見ていきましょう。
Partial<T> ― 全プロパティをオプショナルにする
Partial<T> は、型 T のすべてのプロパティをオプショナル(任意)に変換するユーティリティ型です。オブジェクトの「部分的な更新」を型安全に表現するときに最も使います。
基本構文
Partial の基本
interface User {
id: number;
name: string;
email: string;
}
// Partial<User> は以下と同等:
// { id?: number; name?: string; email?: string; }
const update: Partial<User> = {
name: '田中太郎' // email や id は省略OK
};
内部実装
Partial の内部実装は、Mapped Types を使ったシンプルなものです。
Partial の内部実装
// TypeScript 標準ライブラリの定義
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 仕組み:
// 1. keyof T で T の全プロパティキーを列挙
// 2. [P in keyof T] で各キーをイテレーション
// 3. ? を付加して各プロパティをオプショナルに
// 4. T[P] で元の値の型を保持
実務ユースケース1: APIの部分更新(PATCH)
REST API の PATCH リクエストでは、変更したいフィールドだけを送信します。Partial はこのパターンに最適です。
Partial × PATCH API
interface Product {
id: number;
name: string;
price: number;
description: string;
stock: number;
}
// 更新関数: id は必須、それ以外は任意
async function updateProduct(
id: number,
data: Partial<Omit<Product, 'id'>>
): Promise<Product> {
const response = await fetch(`/api/products/${id}`, {
method: 'PATCH',
body: JSON.stringify(data),
});
return response.json();
}
// 価格だけ更新 → 型安全!
updateProduct(1, { price: 2980 });
// 存在しないプロパティは指定できない
updateProduct(1, { color: 'red' }); // エラー! color は Product に存在しない
実務ユースケース2: React の State 更新
React の状態管理でも Partial は頻出です。
Partial × React State
interface FormState {
username: string;
email: string;
password: string;
confirmPassword: string;
}
// 部分的な状態更新を型安全に
function updateForm(current: FormState, updates: Partial<FormState>): FormState {
return { ...current, ...updates };
}
注意:Partial は1階層目のプロパティのみオプショナルにします。ネストしたオブジェクトのプロパティは変換されません。ネスト対応が必要な場合は、後述の DeepPartial を使います。
Required<T> ― 全プロパティを必須にする
Required<T> は、型 T のすべてのプロパティからオプショナル修飾子(?)を除去して必須に変換します。Partial の逆操作と考えるとわかりやすいでしょう。
基本構文
Required の基本
interface Config {
host?: string;
port?: number;
ssl?: boolean;
timeout?: number;
}
// Required<Config> は以下と同等:
// { host: string; port: number; ssl: boolean; timeout: number; }
const fullConfig: Required<Config> = {
host: 'localhost',
port: 3000,
ssl: true,
timeout: 5000,
}; // すべてのプロパティが必須
内部実装
Required の内部実装
// TypeScript 標準ライブラリの定義
type Required<T> = {
[P in keyof T]-?: T[P];
};
// -? がポイント: ? 修飾子を「除去」するという意味
// Partial の +? と対になる操作
ポイント:-? は Mapped Types の修飾子除去構文です。+?(Partial で使われる)がオプショナルを「追加」するのに対し、-? はオプショナルを「除去」します。同様に -readonly で読み取り専用を除去できます。
実務ユースケース: デフォルト設定の適用後
設定オブジェクトのデフォルト値を適用した後は、すべてのプロパティが確実に存在します。Required でそれを型に反映できます。
Required × デフォルト設定
interface AppConfig {
theme?: 'light' | 'dark';
language?: string;
fontSize?: number;
showSidebar?: boolean;
}
const defaultConfig: Required<AppConfig> = {
theme: 'light',
language: 'ja',
fontSize: 14,
showSidebar: true,
};
function createConfig(userConfig: AppConfig): Required<AppConfig> {
return { ...defaultConfig, ...userConfig };
}
const config = createConfig({ theme: 'dark' });
// config.fontSize は number(undefined の可能性がない)
Readonly<T> ― 全プロパティを読み取り専用にする
Readonly<T> は、型 T のすべてのプロパティにreadonly 修飾子を付加するユーティリティ型です。イミュータブル(不変)なデータを型で保証したいときに使います。
基本構文
Readonly の基本
interface User {
id: number;
name: string;
email: string;
}
const user: Readonly<User> = {
id: 1,
name: '田中太郎',
email: 'tanaka@example.com',
};
user.name = '佐藤次郎';
// エラー! Cannot assign to 'name' because it is a read-only property.
内部実装
Readonly の内部実装
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
実務ユースケース: 関数引数の保護と State 管理
関数が受け取ったオブジェクトを変更しないことを型で保証でき、Redux/Zustand のような状態管理パターンに最適です。
Readonly × 引数の保護
function calculateTax(order: Readonly<{ total: number; items: string[] }>): number {
// order.total = 0; // エラー! read-only
return Math.floor(order.total * 0.1);
}
注意:Readonly は「浅い(シャロー)」変換です。ネストしたオブジェクトのプロパティは読み取り専用になりません。深い読み取り専用が必要な場合は、後述の DeepReadonly を使います。
Partial / Required / Readonly の比較
| ユーティリティ型 |
操作 |
内部キー |
主な用途 |
Partial<T> |
全プロパティを optional |
? |
部分更新、PATCH API |
Required<T> |
全プロパティを required |
-? |
デフォルト適用後 |
Readonly<T> |
全プロパティを readonly |
readonly |
イミュータブルデータ |
Record<K, T> ― キーと値の型を指定してオブジェクト型を作る
Record<K, T> は、キーの型 K と値の型 T を指定してオブジェクト型を構築するユーティリティ型です。辞書型・マップ型のデータ構造を型安全に扱うときに活躍します。
基本構文
Record の基本
// キーが string、値が number のオブジェクト
const scores: Record<string, number> = {
math: 90,
english: 85,
science: 92,
};
// リテラル型でキーを限定
type Role = 'admin' | 'editor' | 'viewer';
const permissions: Record<Role, string[]> = {
admin: ['read', 'write', 'delete'],
editor: ['read', 'write'],
viewer: ['read'],
}; // 3つのキーがすべて必須!
内部実装
Record の内部実装
type Record<K extends keyof any, T> = {
[P in K]: T;
};
// K extends keyof any = K は string | number | symbol のいずれか
// オブジェクトのキーになれる型のみ受け付ける
実務ユースケース1: ステータス管理
Record × ステータス設定
type Status = 'pending' | 'active' | 'inactive' | 'banned';
interface StatusConfig {
label: string;
color: string;
icon: string;
}
// すべてのステータスに対してConfigを定義(漏れがあるとエラー)
const statusMap: Record<Status, StatusConfig> = {
pending: { label: '審査中', color: '#f59e0b', icon: 'clock' },
active: { label: '有効', color: '#10b981', icon: 'check' },
inactive: { label: '無効', color: '#6b7280', icon: 'pause' },
banned: { label: '停止', color: '#ef4444', icon: 'ban' },
};
実務ユースケース2: i18n(多言語対応)
Record × i18n
type Lang = 'ja' | 'en' | 'zh';
type TranslationKey = 'greeting' | 'farewell' | 'thanks';
const translations: Record<Lang, Record<TranslationKey, string>> = {
ja: { greeting: 'こんにちは', farewell: 'さようなら', thanks: 'ありがとう' },
en: { greeting: 'Hello', farewell: 'Goodbye', thanks: 'Thank you' },
zh: { greeting: '你好', farewell: '再见', thanks: '谢谢' },
};
ポイント:Record をリテラル型のUnionと組み合わせると、すべてのキーが必須になるため、定義漏れをコンパイル時に検出できます。ステータス管理、i18n、ルーティング設定など「すべてのパターンを網羅する」場面で強力です。
Pick<T, K> ― 特定のプロパティだけを抽出する
Pick<T, K> は、型 T から指定したプロパティだけを選んで新しい型を作るユーティリティ型です。大きな型から必要な部分だけを取り出すときに使います。
基本構文
Pick の基本
interface User {
id: number;
name: string;
email: string;
age: number;
createdAt: Date;
}
// name と email だけを持つ型
type UserProfile = Pick<User, 'name' | 'email'>;
// { name: string; email: string; }
// id と name だけを持つ型
type UserSummary = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }
内部実装
Pick の内部実装
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// K extends keyof T → K は T のプロパティ名のみ指定可能
// 存在しないプロパティ名を指定するとコンパイルエラー
実務ユースケース: APIレスポンスの絞り込み
Pick × APIレスポンス
// API が返す完全なユーザー型
interface ApiUser {
id: number;
name: string;
email: string;
passwordHash: string;
role: string;
lastLogin: Date;
createdAt: Date;
}
// フロントエンドに公開して良い情報だけを抽出
type PublicUser = Pick<ApiUser, 'id' | 'name' | 'role'>;
// リスト表示用の最小限の型
type UserListItem = Pick<ApiUser, 'id' | 'name' | 'email'>;
Omit<T, K> ― 特定のプロパティを除外する
Omit<T, K> は、型 T から指定したプロパティを除外した新しい型を作ります。Pick の逆操作です。「この数個のプロパティ以外は全部欲しい」という場面で使います。
基本構文
Omit の基本
interface User {
id: number;
name: string;
email: string;
passwordHash: string;
createdAt: Date;
}
// passwordHash を除外
type SafeUser = Omit<User, 'passwordHash'>;
// { id: number; name: string; email: string; createdAt: Date; }
// 複数除外もOK
type UserInput = Omit<User, 'id' | 'createdAt'>;
// { name: string; email: string; passwordHash: string; }
内部実装
Omit の内部実装
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// 仕組み:
// 1. keyof T で T の全キーを取得
// 2. Exclude で K を除外したキーの集合を得る
// 3. Pick でその残りのキーだけを T から抽出
実務ユースケース: 新規作成フォームの型
Omit × 新規作成
interface Article {
id: number;
title: string;
body: string;
author: string;
createdAt: Date;
updatedAt: Date;
}
// 新規作成時は id, createdAt, updatedAt はサーバー側で生成
type CreateArticle = Omit<Article, 'id' | 'createdAt' | 'updatedAt'>;
// { title: string; body: string; author: string; }
// 更新時は id が必要、タイムスタンプはサーバー管理
type UpdateArticle = Partial<Omit<Article, 'id' | 'createdAt' | 'updatedAt'>>;
Pick と Omit の使い分け
| 場面 |
使うべき型 |
理由 |
| 欲しいプロパティが少数 |
Pick |
必要なものを明示的に列挙 |
| 除外したいプロパティが少数 |
Omit |
不要なものだけを指定 |
| 新しいプロパティが元に追加されたら自動追従したい |
Omit |
明示的に除外したもの以外は自動で含まれる |
Exclude<T, U> ― Union型から特定の型を除外する
Exclude<T, U> は、Union型 T から U に代入可能なメンバーを除外します。Pick/Omit がオブジェクトのプロパティを操作するのに対し、Exclude はUnion型のメンバーを操作する点が異なります。
基本構文
Exclude の基本
type AllColors = 'red' | 'green' | 'blue' | 'yellow';
// 'red' と 'green' を除外
type CoolColors = Exclude<AllColors, 'red' | 'yellow'>;
// 'green' | 'blue'
// 型レベルで Union メンバーを除外
type T1 = Exclude<string | number | boolean, boolean>;
// string | number
内部実装
Exclude の内部実装
type Exclude<T, U> = T extends U ? never : T;
// Conditional Types の分配法則(Distributive)が働く:
// Exclude<'a' | 'b' | 'c', 'a'>
// = ('a' extends 'a' ? never : 'a') | ('b' extends 'a' ? never : 'b') | ('c' extends 'a' ? never : 'c')
// = never | 'b' | 'c'
// = 'b' | 'c'
ポイント:Exclude が動作する鍵は Conditional Types の分配法則(Distributive Conditional Types)です。Union型の各メンバーに対して条件が個別に評価され、条件を満たすものが never(= 消える)になります。
実務ユースケース: イベント型の絞り込み
Exclude × イベント型の絞り込み
type Event =
| { type: 'click'; x: number; y: number }
| { type: 'keydown'; key: string }
| { type: 'scroll'; offset: number };
// scroll イベントを除外
type InteractiveEvent = Exclude<Event, { type: 'scroll' }>;
// { type: 'click'; x: number; y: number } | { type: 'keydown'; key: string }
Extract<T, U> ― Union型から特定の型だけを抽出する
Extract<T, U> は Exclude の逆で、Union型 T から U に代入可能なメンバーだけを抽出します。
基本構文
Extract の基本
type AllTypes = string | number | boolean | null | undefined;
// string と number だけを抽出
type Primitives = Extract<AllTypes, string | number>;
// string | number
内部実装
Extract の内部実装
type Extract<T, U> = T extends U ? T : never;
// Exclude の逆: 条件を満たすものを「残す」
実務ユースケース: 共通の型を抽出する
Extract × 共通型の抽出
type AdminPermission = 'read' | 'write' | 'delete' | 'manage';
type EditorPermission = 'read' | 'write' | 'publish';
// 両者に共通する権限を抽出
type SharedPermission = Extract<AdminPermission, EditorPermission>;
// 'read' | 'write'
Exclude と Extract の比較
| 型 |
操作 |
内部 |
対象 |
Exclude<T,U> |
U に該当するものを除外 |
T extends U ? never : T |
Union型のメンバー |
Extract<T,U> |
U に該当するものだけ残す |
T extends U ? T : never |
Union型のメンバー |
Omit<T,K> |
K のプロパティを除外 |
Pick<T, Exclude<keyof T, K>> |
オブジェクトのプロパティ |
Pick<T,K> |
K のプロパティだけ残す |
[P in K]: T[P] |
オブジェクトのプロパティ |
NonNullable<T> ― null と undefined を除外する
NonNullable<T> は、型 T から null と undefined を除外します。実質的に Exclude<T, null | undefined> と同じです。
基本構文
NonNullable の基本
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string
type T1 = NonNullable<string | number | null>;
// string | number
内部実装
NonNullable の内部実装
// TypeScript 4.8以降
type NonNullable<T> = T & {};
// TypeScript 4.7以前
type NonNullable<T> = T extends null | undefined ? never : T;
実務ユースケース: null チェック後の型
NonNullable × 環境変数の安全な取得
// process.env の値は string | undefined
function getEnvVar(key: string): NonNullable<string | undefined> {
const value = process.env[key];
if (value === undefined) {
throw new Error(`環境変数 ${key} が設定されていません`);
}
return value; // string が返る(undefined ではない)
}
const dbUrl = getEnvVar('DATABASE_URL');
// dbUrl の型は string(undefined がない)
ReturnType<T> ― 関数の戻り値型を取得する
ReturnType<T> は、関数型 T の戻り値の型を取得するユーティリティ型です。サードパーティライブラリの関数や、型定義がエクスポートされていない関数の戻り値型を取得するときに重宝します。
基本構文
ReturnType の基本
function getUser() {
return { id: 1, name: '太郎', age: 25 };
}
// 関数の戻り値から型を推論
type User = ReturnType<typeof getUser>;
// { id: number; name: string; age: number; }
// 組み込み関数にも使える
type T1 = ReturnType<() => string>; // string
type T2 = ReturnType<() => void>; // void
type T3 = ReturnType<() => never>; // never
内部実装
ReturnType の内部実装
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
// infer R で戻り値の型を推論変数 R にキャプチャ
// T が関数型なら R(戻り値の型)を返す
実務ユースケース: ライブラリ関数の戻り値型を再利用
ReturnType × ライブラリ関数
// ライブラリが型をエクスポートしていない場合
import { createStore } from 'some-library';
// 関数の戻り値から型を取得
type Store = ReturnType<typeof createStore>;
// 自作関数のテストでも活用
function createApiClient(baseUrl: string) {
return {
get: (path: string) => fetch(`${baseUrl}${path}`),
post: (path: string, data: unknown) => fetch(`${baseUrl}${path}`, { method: 'POST', body: JSON.stringify(data) }),
};
}
type ApiClient = ReturnType<typeof createApiClient>;
// { get: (path: string) => Promise<Response>; post: ... }
注意:ReturnType に渡すのは型であり、値ではありません。関数の値から型を取るには typeof を前置します: ReturnType<typeof myFunction>
Parameters<T> ― 関数の引数型をタプルで取得する
Parameters<T> は、関数型 T の引数の型をタプル型で取得します。既存の関数と同じ引数シグネチャを持つラッパー関数を作るときに便利です。
基本構文
Parameters の基本
function greet(name: string, age: number): string {
return `${name}さん(${age}歳)`;
}
type GreetParams = Parameters<typeof greet>;
// [name: string, age: number]
// タプルの各要素にインデックスでアクセス
type FirstParam = GreetParams[0]; // string
type SecondParam = GreetParams[1]; // number
内部実装
Parameters の内部実装
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
// infer P で引数リストをタプル型としてキャプチャ
実務ユースケース: 関数のラッパー作成
Parameters × ラッパー関数
// 元の関数
function fetchUser(id: number, options?: { cache: boolean }): Promise<User> {
/* ... */
}
// ロギング付きラッパー(引数の型を自動で合わせる)
function withLogging(
...args: Parameters<typeof fetchUser>
): ReturnType<typeof fetchUser> {
console.log('fetchUser called with:', args);
return fetchUser(...args);
}
ConstructorParameters<T> と InstanceType<T> ― クラス型の操作
ConstructorParameters はコンストラクタの引数型をタプルで取得し、InstanceType はクラスのインスタンス型を取得します。ファクトリパターンや DI コンテナで活躍します。
基本構文
ConstructorParameters と InstanceType
class User {
constructor(
public name: string,
public age: number,
public email: string
) {}
}
// コンストラクタの引数型
type UserArgs = ConstructorParameters<typeof User>;
// [name: string, age: number, email: string]
// インスタンスの型
type UserInstance = InstanceType<typeof User>;
// User(= { name: string; age: number; email: string })
実務ユースケース: ファクトリ関数
InstanceType × ファクトリパターン
// 任意のクラスをインスタンス化するファクトリ
function createInstance<T extends new (...args: any[]) => any>(
ctor: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
return new ctor(...args);
}
const user = createInstance(User, '太郎', 25, 'taro@example.com');
// user の型は User — 型安全にインスタンス化できる
Awaited<T> ― Promiseの解決値の型を取得する
Awaited<T>(TypeScript 4.5+)は、Promise を再帰的にアンラップして最終的な解決値の型を取得します。Promise<Promise<string>> のようなネストにも対応します。
基本構文
Awaited の基本
type A = Awaited<Promise<string>>;
// string
type B = Awaited<Promise<Promise<number>>>;
// number(再帰的にアンラップ)
type C = Awaited<string | Promise<number>>;
// string | number
実務ユースケース: async関数の戻り値型
Awaited × async関数
async function fetchUsers(): Promise<User[]> {
const res = await fetch('/api/users');
return res.json();
}
// ReturnType は Promise<User[]> を返す
type T1 = ReturnType<typeof fetchUsers>; // Promise<User[]>
// Awaited と組み合わせて Promise をアンラップ
type T2 = Awaited<ReturnType<typeof fetchUsers>>; // User[]
文字列操作型 ― Uppercase / Lowercase / Capitalize / Uncapitalize
TypeScript 4.1 で導入されたテンプレートリテラル型と連携する4つの組み込みユーティリティ型です。文字列リテラル型に対して、大文字・小文字の変換を行います。
基本構文
文字列操作型の基本
type T1 = Uppercase<'hello'>; // 'HELLO'
type T2 = Lowercase<'HELLO'>; // 'hello'
type T3 = Capitalize<'hello'>; // 'Hello'
type T4 = Uncapitalize<'Hello'>; // 'hello'
実務ユースケース: イベントハンドラの型生成
Capitalize × イベントハンドラ型の自動生成
type EventName = 'click' | 'focus' | 'blur';
// 'onClick' | 'onFocus' | 'onBlur' を自動生成
type EventHandler = `on${Capitalize<EventName>}`;
// 'onClick' | 'onFocus' | 'onBlur'
// ハンドラプロパティを持つ型を自動生成
type EventHandlers = {
[K in EventHandler]: () => void;
};
// { onClick: () => void; onFocus: () => void; onBlur: () => void; }
実務ユースケース: getter/setter の型生成
Capitalize × getter/setter
type Getters<T> = {
[K in keyof T as `get${Capitalize<K & string>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }
ThisParameterType / OmitThisParameter / ThisType ― this の型操作
TypeScript では関数の最初の引数に this パラメータを宣言して this の型を指定できます。これらの3つのユーティリティ型は、その this パラメータを操作します。
ThisParameterType<T>
関数型の this パラメータの型を取得します。
ThisParameterType
function toHex(this: Number) {
return this.toString(16);
}
type T = ThisParameterType<typeof toHex>;
// Number
OmitThisParameter<T>
関数型から this パラメータを除去した型を返します。Function.prototype.bind の戻り値の型付けに使われます。
OmitThisParameter
function toHex(this: Number) {
return this.toString(16);
}
// this を除去して通常の関数型に
type PureToHex = OmitThisParameter<typeof toHex>;
// () => string
const bound: PureToHex = toHex.bind(255);
console.log(bound()); // 'ff'
ThisType<T>
ThisType は特殊で、型変換は行いません。オブジェクトリテラル内のメソッドで this の型を指定するマーカーとして使います(--noImplicitThis が必要)。
ThisType × Vue風オプションAPI
interface ComponentOptions<D, M> {
data?: () => D;
methods?: M & ThisType<D & M>; // methods 内の this は D & M
}
function defineComponent<D, M>(options: ComponentOptions<D, M>) { /* ... */ }
defineComponent({
data() {
return { count: 0 };
},
methods: {
increment() {
this.count++; // this.count は number と推論される!
},
reset() {
this.count = 0;
this.increment(); // this.increment() も型安全に呼べる
},
},
});
複数のユーティリティ型を組み合わせる実務パターン集
ユーティリティ型の真価は、複数を組み合わせることで発揮されます。ここでは実務でよく使う組み合わせパターンを紹介します。
パターン1: 特定プロパティだけオプショナルにする
Partial は全プロパティをオプショナルにしますが、一部だけオプショナルにしたい場合は Omit と Partial + Pick を組み合わせます。
特定プロパティだけオプショナルに
// 汎用ヘルパー型
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
interface User {
id: number;
name: string;
email: string;
age: number;
}
// email と age だけオプショナルに
type CreateUser = Optional<User, 'email' | 'age'>;
// { id: number; name: string; email?: string; age?: number; }
パターン2: 特定プロパティだけ必須にする
特定プロパティだけ必須に
type SetRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
interface Options {
width?: number;
height?: number;
color?: string;
label?: string;
}
// width と height だけ必須に
type SizedOptions = SetRequired<Options, 'width' | 'height'>;
// { width: number; height: number; color?: string; label?: string; }
パターン3: 特定プロパティだけ読み取り専用にする
特定プロパティだけ readonly に
type SetReadonly<T, K extends keyof T> = Omit<T, K> & Readonly<Pick<T, K>>;
// id と createdAt だけ読み取り専用
type UserEntity = SetReadonly<User, 'id'>;
パターン4: プロパティの型を上書きする
プロパティの型を上書き
type Override<T, U> = Omit<T, keyof U> & U;
interface ApiResponse {
id: number;
createdAt: string; // API は ISO文字列を返す
updatedAt: string;
}
// フロントでは Date に変換して使う
type AppModel = Override<ApiResponse, {
createdAt: Date;
updatedAt: Date;
}>;
// { id: number; createdAt: Date; updatedAt: Date; }
パターン5: CRUDの4つの型を一括で派生する
CRUD型の一括派生
interface Article {
id: number;
title: string;
body: string;
author: string;
createdAt: Date;
updatedAt: Date;
}
// サーバー自動生成フィールド
type AutoFields = 'id' | 'createdAt' | 'updatedAt';
// 一覧表示用(軽量)
type ArticleListItem = Pick<Article, 'id' | 'title' | 'author' | 'createdAt'>;
// 新規作成用
type CreateArticle = Omit<Article, AutoFields>;
// 更新用(部分更新可能)
type UpdateArticle = Partial<Omit<Article, AutoFields>>;
// 読み取り専用(詳細表示用)
type ArticleDetail = Readonly<Article>;
ポイント:1つのマスター型(Article)を Single Source of Truth にして、ユーティリティ型で派生することで、プロパティの追加・変更に対してすべての型が自動的に追従します。
カスタムユーティリティ型の作り方
TypeScript 標準のユーティリティ型では対応できないケースもあります。ここでは、実務で役立つカスタムユーティリティ型の作り方を解説します。
DeepPartial ― ネストしたプロパティもすべてオプショナルに
Partial は1階層のみですが、DeepPartial はネストしたオブジェクトのプロパティも再帰的にオプショナルにします。
DeepPartial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
// 使用例
interface Config {
database: {
host: string;
port: number;
credentials: {
user: string;
password: string;
};
};
cache: { ttl: number };
}
// 深いネストの一部だけ指定できる
const overrides: DeepPartial<Config> = {
database: {
credentials: { password: 'newpass' },
},
};
DeepReadonly ― ネストしたプロパティもすべて読み取り専用に
DeepReadonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
const config: DeepReadonly<Config> = { /* ... */ };
// config.database.host = 'new'; // エラー! 深いプロパティも readonly
Mutable ― readonly を除去する
Mutable(Readonly の逆)
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
interface FrozenUser {
readonly id: number;
readonly name: string;
}
type EditableUser = Mutable<FrozenUser>;
// { id: number; name: string; } — readonly が除去された
Nullable / NonNullableProps
Nullable と NonNullableProps
// 全プロパティを nullable に
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
// 全プロパティから null/undefined を除去
type NonNullableProps<T> = {
[P in keyof T]: NonNullable<T[P]>;
};
ValueOf ― オブジェクト型の値の Union を取得
ValueOf
type ValueOf<T> = T[keyof T];
const STATUS = {
ACTIVE: 'active',
INACTIVE: 'inactive',
PENDING: 'pending',
} as const;
type StatusValue = ValueOf<typeof STATUS>;
// 'active' | 'inactive' | 'pending'
PickByType ― 特定の値の型を持つプロパティだけ抽出
PickByType
type PickByType<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K];
};
interface User {
id: number;
name: string;
email: string;
age: number;
isActive: boolean;
}
// string 型のプロパティだけ抽出
type StringProps = PickByType<User, string>;
// { name: string; email: string; }
カスタムユーティリティ型 まとめ
DeepPartial<T> ― ネスト対応の Partial
DeepReadonly<T> ― ネスト対応の Readonly
Mutable<T> ― readonly を除去(Readonly の逆)
Optional<T, K> ― 指定プロパティだけオプショナル
SetRequired<T, K> ― 指定プロパティだけ必須
SetReadonly<T, K> ― 指定プロパティだけ readonly
Override<T, U> ― プロパティの型を上書き
Nullable<T> ― 全プロパティを nullable に
ValueOf<T> ― 値の型の Union を取得
PickByType<T, V> ― 値の型でフィルタリング
よくあるエラーと対処法
ユーティリティ型を使う際に遭遇しやすいエラーとその対処法をまとめます。
エラー1: Type … does not satisfy the constraint ‘keyof …’
Pick や Omit で、存在しないプロパティ名を指定したときに発生します。
Pick のキー制約エラー
interface User {
id: number;
name: string;
}
// エラー: Type '"age"' does not satisfy the constraint '"id" | "name"'
type T = Pick<User, 'age'>;
// 対処: 存在するプロパティ名のみ指定する
type T2 = Pick<User, 'id' | 'name'>; // OK
ポイント:Pick は K extends keyof T の制約があるため、存在しないキーを指定するとエラーになります。一方、Omit は K extends keyof any なので、存在しないキーを指定してもエラーになりません(単に無視される)。
エラー2: typeof を忘れて関数値を直接渡す
typeof を忘れるエラー
function getUser() { return { id: 1, name: '太郎' }; }
// エラー: 'getUser' refers to a value, but is being used as a type here
type T = ReturnType<getUser>;
// 対処: typeof を使って型に変換する
type T2 = ReturnType<typeof getUser>; // OK
エラー3: Readonly の浅さを見落とす
Readonly の浅さ問題
interface State {
user: { name: string };
items: string[];
}
const state: Readonly<State> = { user: { name: '太郎' }, items: ['a'] };
// state.user = {}; // エラー(1階層目は保護される)
state.user.name = '次郎'; // エラーにならない!(2階層目は保護されない)
state.items.push('b'); // エラーにならない!(配列のメソッドは呼べる)
// 対処: DeepReadonly を使う、または as const を使う
エラー4: Record のキーに string を使うと undefined の可能性
Record<string, T> のアクセス安全性
const map: Record<string, number> = { a: 1 };
// 型的には number だが、実際は undefined の可能性あり
const val = map['nonexistent']; // number(実際は undefined)
// 対処1: noUncheckedIndexedAccess を有効にする
// 対処2: リテラル型で限定する Record<'a' | 'b', number>
注意:tsconfig.json の noUncheckedIndexedAccess: true を有効にすると、Record<string, T> のインデックスアクセスが T | undefined を返すようになり、安全性が向上します。
まとめ ― ユーティリティ型 全早見表
この記事で解説したすべてのユーティリティ型を早見表にまとめます。
| ユーティリティ型 |
効果 |
代表的な用途 |
Partial<T> |
全プロパティをオプショナルに |
PATCH API、部分更新、React State |
Required<T> |
全プロパティを必須に |
デフォルト値適用後、バリデーション後 |
Readonly<T> |
全プロパティを読み取り専用に |
State管理、引数保護 |
Record<K, T> |
キーKと値Tのオブジェクト型 |
マップ型、ステータス管理、i18n |
Pick<T, K> |
指定プロパティのみ抽出 |
API公開型、リスト表示用型 |
Omit<T, K> |
指定プロパティを除外 |
新規作成フォーム、機密情報除外 |
Exclude<T, U> |
UnionからUを除外 |
イベント型絞り込み、型フィルタ |
Extract<T, U> |
UnionからUだけ抽出 |
共通型の抽出、型の交差 |
NonNullable<T> |
null/undefinedを除外 |
null チェック後の型付け |
ReturnType<T> |
関数の戻り値型を取得 |
ライブラリ関数の型再利用 |
Parameters<T> |
関数の引数型をタプルで取得 |
ラッパー関数、デコレータ |
ConstructorParameters<T> |
コンストラクタの引数型 |
ファクトリパターン |
InstanceType<T> |
クラスのインスタンス型 |
DIコンテナ、ファクトリ |
Awaited<T> |
Promiseの解決値の型 |
async関数の戻り値型取得 |
Uppercase/Lowercase |
文字列リテラル型の大文字/小文字変換 |
Template Literal Types |
Capitalize/Uncapitalize |
先頭文字の大文字/小文字変換 |
イベントハンドラ、getter/setter生成 |
ThisParameterType<T> |
this パラメータの型を取得 |
this 型の検査 |
OmitThisParameter<T> |
this パラメータを除去 |
bind 後の関数型 |
ThisType<T> |
this の型をマーク |
Vue風 Options API |
ユーティリティ型は TypeScript の型システムを実用的にする中核的な機能です。最初は Partial、Pick、Omit、Record の4つから使い始め、慣れてきたら Exclude/Extract/ReturnType へと広げていくのがおすすめです。
ポイントは、「1つのマスター型を定義して、ユーティリティ型で派生する」という設計パターンです。これにより型の重複を排除し、変更に強い型設計が実現できます。
さらに、標準のユーティリティ型で足りない場合は、Mapped Types・Conditional Types・infer を組み合わせてカスタムユーティリティ型を作ることで、プロジェクト固有の型変換パターンも型安全に表現できます。