メインコンテンツまでスキップ

ユーティリティ型

この章では、TypeScriptに組み込まれているユーティリティ型(Utility Types)について学びます。

この章で学ぶこと

  • Partial<T>Required<T>の使い方
  • Readonly<T>で読み取り専用型を作る
  • Pick<T, K>Omit<T, K>によるプロパティの選択と除外
  • Record<K, T>でオブジェクト型を作成する
  • Exclude<T, U>Extract<T, U>でUnion型を操作する
  • NonNullable<T>でnull/undefinedを除外する
  • ReturnType<T>Parameters<T>で関数の型を取得する
備考

ユーティリティ型を組み合わせると、既存の型から新しい型を導出でき、コードを再利用しやすくなります。

Partial<T>

Partial<T>は、すべてのプロパティをオプショナル(省略可能)にします。

interface User {
id: number;
name: string;
email: string;
age: number;
}

// すべてのプロパティがオプショナルになる
type PartialUser = Partial<User>;
// 結果:
// {
// id?: number;
// name?: string;
// email?: string;
// age?: number;
// }

実用例:更新関数

// Partial<User>を使った更新関数
// 更新したいプロパティだけを渡せる
function updateUser(user: User, updates: Partial<User>): User {
// スプレッド構文で元のオブジェクトに更新を適用
return { ...user, ...updates };
}

const user: User = {
id: 1,
name: "山田太郎",
email: "yamada@example.com",
age: 30
};

// ageだけ更新(他のプロパティは省略可能)
const updated = updateUser(user, { age: 31 });
console.log(updated);
// { id: 1, name: "山田太郎", email: "yamada@example.com", age: 31 }

// 複数のプロパティを更新
const updated2 = updateUser(user, { name: "山田次郎", age: 25 });
console.log(updated2);
// { id: 1, name: "山田次郎", email: "yamada@example.com", age: 25 }

Required<T>

Required<T>は、すべてのプロパティを必須にします(Partialの逆)。

interface PartialConfig {
apiUrl?: string;
timeout?: number;
retryCount?: number;
}

// すべてのプロパティが必須になる
type FullConfig = Required<PartialConfig>;
// 結果:
// {
// apiUrl: string;
// timeout: number;
// retryCount: number;
// }

const config: FullConfig = {
apiUrl: "https://api.example.com",
timeout: 5000,
retryCount: 3
// すべて必須なので、1つでも欠けるとエラー
};

実用例:設定の検証

// オプショナルな入力を受け取り、デフォルト値で補完
function createConfig(input: PartialConfig): Required<PartialConfig> {
return {
apiUrl: input.apiUrl ?? "https://api.default.com",
timeout: input.timeout ?? 3000,
retryCount: input.retryCount ?? 3
};
}

// 一部だけ指定
const config1 = createConfig({ apiUrl: "https://api.custom.com" });
console.log(config1);
// { apiUrl: "https://api.custom.com", timeout: 3000, retryCount: 3 }

Readonly<T>

Readonly<T>は、すべてのプロパティを読み取り専用にします。

interface Config {
apiUrl: string;
timeout: number;
retryCount: number;
}

// すべてのプロパティが読み取り専用になる
type ReadonlyConfig = Readonly<Config>;
// 結果:
// {
// readonly apiUrl: string;
// readonly timeout: number;
// readonly retryCount: number;
// }

const config: ReadonlyConfig = {
apiUrl: "https://api.example.com",
timeout: 5000,
retryCount: 3
};

// 変更しようとするとエラー
// config.timeout = 10000;
// Error: Cannot assign to 'timeout' because it is a read-only property.

実用例:イミュータブルな状態管理

interface AppState {
user: { name: string; loggedIn: boolean };
theme: "light" | "dark";
notifications: string[];
}

// 状態を読み取り専用として扱う
function processState(state: Readonly<AppState>): void {
console.log(`ユーザー: ${state.user.name}`);
console.log(`テーマ: ${state.theme}`);

// stateを変更しようとするとエラー
// state.theme = "dark"; // Error
}

const initialState: AppState = {
user: { name: "山田太郎", loggedIn: true },
theme: "light",
notifications: []
};

processState(initialState);

Pick<T, K>

Pick<T, K>は、型Tから指定したプロパティKのみを抽出します。

interface User {
id: number;
name: string;
email: string;
age: number;
address: string;
}

// nameとemailのみを持つ型
type UserContact = Pick<User, "name" | "email">;
// 結果:
// {
// name: string;
// email: string;
// }

const contact: UserContact = {
name: "山田太郎",
email: "yamada@example.com"
// idやageは不要(指定できない)
};

実用例:関数の引数を限定

// ログイン時に必要な情報だけを受け取る
function login(credentials: Pick<User, "email">): void {
console.log(`Logging in: ${credentials.email}`);
}

// 表示に必要な情報だけを受け取る
function displayUserCard(user: Pick<User, "name" | "age">): void {
console.log(`${user.name} (${user.age}歳)`);
}

const fullUser: User = {
id: 1,
name: "山田太郎",
email: "yamada@example.com",
age: 30,
address: "東京都"
};

login(fullUser); // OK
displayUserCard(fullUser); // OK

Omit<T, K>

Omit<T, K>は、型Tから指定したプロパティKを除外します(Pickの逆)。

interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}

// passwordを除外した型
type PublicUser = Omit<User, "password">;
// 結果:
// {
// id: number;
// name: string;
// email: string;
// createdAt: Date;
// }

// 複数のプロパティを除外
type UserWithoutMeta = Omit<User, "id" | "createdAt">;
// 結果:
// {
// name: string;
// email: string;
// password: string;
// }

実用例:APIレスポンスで機密情報を除外

// 内部でフルデータを持つ
const internalUser: User = {
id: 1,
name: "山田太郎",
email: "yamada@example.com",
password: "hashedPassword123",
createdAt: new Date()
};

// APIレスポンス用に機密情報を除外する関数
function toPublicUser(user: User): PublicUser {
// 分割代入でpasswordを除外し、残りを返す
const { password, ...publicInfo } = user;
return publicInfo;
}

const publicUser = toPublicUser(internalUser);
console.log(publicUser);
// { id: 1, name: "山田太郎", email: "yamada@example.com", createdAt: ... }
// passwordは含まれない

Record<K, T>

Record<K, T>は、キーの型Kと値の型Tを指定してオブジェクト型を作成します。

// 文字列キーでnumber値のオブジェクト
type StringNumberMap = Record<string, number>;

const scores: StringNumberMap = {
math: 90,
english: 85,
science: 92
};

実用例:特定のキーに制限

// グレードをキーとして制限
type Grade = "A" | "B" | "C" | "D" | "F";
type GradeCount = Record<Grade, number>;

const gradeDistribution: GradeCount = {
A: 10,
B: 20,
C: 30,
D: 15,
F: 5
// すべてのグレードが必須
};

// HTTPステータスコードの説明
type HttpStatus = 200 | 404 | 500;
type StatusMessages = Record<HttpStatus, string>;

const messages: StatusMessages = {
200: "成功",
404: "見つかりません",
500: "サーバーエラー"
};

実用例:動的なマッピング

// ユーザーIDをキーとしたユーザーマップ
interface User {
id: number;
name: string;
}

type UserMap = Record<number, User>;

const users: UserMap = {
1: { id: 1, name: "山田太郎" },
2: { id: 2, name: "田中花子" },
3: { id: 3, name: "鈴木次郎" }
};

// IDでユーザーを取得
function getUser(id: number): User | undefined {
return users[id];
}

Exclude<T, U>

Exclude<T, U>は、Union型Tから型Uに該当するものを除外します。

type AllStatus = "pending" | "approved" | "rejected" | "cancelled";

// "cancelled"を除外
type ActiveStatus = Exclude<AllStatus, "cancelled">;
// 結果: "pending" | "approved" | "rejected"

// 複数の型を除外
type OngoingStatus = Exclude<AllStatus, "approved" | "rejected">;
// 結果: "pending" | "cancelled"

実用例:条件によるフィルタリング

// すべての型
type AllTypes = string | number | boolean | null | undefined;

// null/undefinedを除外
type PrimitiveTypes = Exclude<AllTypes, null | undefined>;
// 結果: string | number | boolean

// 文字列以外を除外
type NonStringTypes = Exclude<AllTypes, string>;
// 結果: number | boolean | null | undefined

Extract<T, U>

Extract<T, U>は、Union型Tから型Uに該当するもののみを抽出します(Excludeの逆)。

type AllTypes = string | number | boolean | null | undefined;

// stringとnumberのみ抽出
type StringOrNumber = Extract<AllTypes, string | number>;
// 結果: string | number

// booleanのみ抽出
type BooleanOnly = Extract<AllTypes, boolean>;
// 結果: boolean

実用例:共通の型を抽出

type AdminPermissions = "read" | "write" | "delete" | "admin";
type UserPermissions = "read" | "write" | "comment";

// 両方に共通する権限を抽出
type CommonPermissions = Extract<AdminPermissions, UserPermissions>;
// 結果: "read" | "write"

NonNullable<T>

NonNullable<T>は、型Tからnullとundefinedを除外します。

type MaybeNumber = number | null | undefined;
type DefiniteNumber = NonNullable<MaybeNumber>;
// 結果: number

type MaybeString = string | null;
type DefiniteString = NonNullable<MaybeString>;
// 結果: string

実用例:値の存在を保証

function processValue(value: number | null | undefined): void {
if (value !== null && value !== undefined) {
// この中ではvalueはnumber型
const definite: NonNullable<typeof value> = value;
console.log(definite * 2);
}
}

// 配列からnull/undefinedを除外
function filterNullish<T>(arr: (T | null | undefined)[]): NonNullable<T>[] {
return arr.filter((item): item is NonNullable<T> => item != null);
}

const mixedArray = [1, null, 2, undefined, 3];
const cleanArray = filterNullish(mixedArray);
console.log(cleanArray); // [1, 2, 3]

ReturnType<T>

ReturnType<T>は、関数型Tの戻り値の型を取得します。

// 関数を定義
function createUser() {
return {
id: 1,
name: "山田太郎",
email: "yamada@example.com"
};
}

// createUser関数の戻り値の型を取得
// typeofで関数の型を取得し、ReturnTypeで戻り値の型を抽出
type User = ReturnType<typeof createUser>;
// 結果:
// {
// id: number;
// name: string;
// email: string;
// }

// 取得した型を使用
const user: User = {
id: 2,
name: "田中花子",
email: "tanaka@example.com"
};

実用例:既存の関数から型を導出

// サードパーティライブラリの関数
function fetchUserData(id: number) {
return {
user: { id, name: "User", email: "user@example.com" },
metadata: { fetchedAt: new Date(), source: "api" }
};
}

// 関数の戻り値の型を再利用
type FetchResult = ReturnType<typeof fetchUserData>;

// キャッシュに保存
const cache: Map<number, FetchResult> = new Map();

function getCachedOrFetch(id: number): FetchResult {
if (cache.has(id)) {
return cache.get(id)!;
}
const result = fetchUserData(id);
cache.set(id, result);
return result;
}

Parameters<T>

Parameters<T>は、関数型Tの引数の型をタプルとして取得します。

function updateUser(id: number, name: string, email: string): void {
console.log(`Updating user ${id}: ${name}, ${email}`);
}

// 引数の型をタプルとして取得
type UpdateUserParams = Parameters<typeof updateUser>;
// 結果: [number, string, string]

// スプレッド演算子で関数呼び出し
const params: UpdateUserParams = [1, "山田太郎", "yamada@example.com"];
updateUser(...params);

実用例:関数のラッパー

function originalFunction(a: number, b: string, c: boolean): string {
return `${a}-${b}-${c}`;
}

// 元の関数と同じ引数を受け取るラッパー関数
function wrappedFunction(...args: Parameters<typeof originalFunction>): string {
console.log("Before:", args);
const result = originalFunction(...args);
console.log("After:", result);
return result;
}

wrappedFunction(42, "hello", true);
// Before: [42, "hello", true]
// After: 42-hello-true

ConstructorParameters<T>

ConstructorParameters<T> は、コンストラクタ関数(クラス)の引数の型をタプルとして取得します。

class User {
constructor(
public id: number,
public name: string,
public email: string,
) {}
}

// コンストラクタの引数の型をタプルとして取得
type UserArgs = ConstructorParameters<typeof User>;
// 結果: [number, string, string]

// ファクトリ関数など、同じ引数でインスタンスを作りたい場面で有用
function createUser(...args: ConstructorParameters<typeof User>): User {
console.log('Creating user with args:', args)
return new User(...args)
}

const user = createUser(1, '山田太郎', 'yamada@example.com')

Awaited<T>(TS 4.5+)

Awaited<T> は、Promise に包まれた値の型を再帰的に取り出します。await で解決した結果の型を取得したいときに使います。

type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number(再帰的に解決)
type C = Awaited<string>; // string(Promiseでなければそのまま)

実用例: 非同期関数の戻り値型を取得

async function fetchUser() {
const response = await fetch('/api/users/1')
return (await response.json()) as { id: number; name: string }
}

// ReturnType は Promise を剥がせないため、Awaited と組み合わせる
type User = Awaited<ReturnType<typeof fetchUser>>;
// 結果: { id: number; name: string }

NoInfer<T>(TS 5.4+)

NoInfer<T> は、その型パラメータの推論対象から特定の引数を除外するユーティリティです。複数箇所で同じ型パラメータが使われるとき、推論の基準を明示的に限定したい場合に使います(例: createStreetLight(colors, defaultColor)defaultColorC の推論に影響させない)。「ここから型を推論してほしくない」という意図を型で表現でき、ライブラリ API の設計で有用です。問題と解決の詳しい解説・コード例は前章ジェネリクスの制約の NoInfer 節を参照してください。

文字列操作型(TS 4.1+)

Template Literal Types と組み合わせて使える組み込みの文字列操作型です。

動作
Uppercase<S>大文字化Uppercase<'hello'>'HELLO'
Lowercase<S>小文字化Lowercase<'WORLD'>'world'
Capitalize<S>先頭を大文字化Capitalize<'user'>'User'
Uncapitalize<S>先頭を小文字化Uncapitalize<'User'>'user'
type EventName<S extends string> = `on${Capitalize<S>}`;

type ClickEvent = EventName<'click'>; // 'onClick'
type SubmitEvent = EventName<'submit'>; // 'onSubmit'

// getter名の自動生成
type Getter<K extends string> = `get${Capitalize<K>}`;
type UserGetters = Getter<'name' | 'email'>;
// 結果: 'getName' | 'getEmail'
備考

これらの文字列操作型は TypeScript 4.1 で導入されて以降、Template Literal Types と組み合わせて型安全な DSL を作る際の基礎となっています。詳細な Template Literal Types は 16 章(Mapped Types と Conditional Types)で扱います。

ユーティリティ型の組み合わせ

複数のユーティリティ型を組み合わせることで、より複雑な型を作成できます。

interface User {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
createdAt: Date;
updatedAt: Date;
}

// 1. 機密情報を除外した公開用の型
type PublicUser = Omit<User, "password">;

// 2. ログイン情報のみの型
type LoginCredentials = Pick<User, "email" | "password">;

// 3. 更新用の型(id, createdAt, updatedAt以外をオプショナルに)
type UserUpdateInput = Partial<Omit<User, "id" | "createdAt" | "updatedAt">>;

// 4. 読み取り専用の公開ユーザー型
type ReadonlyPublicUser = Readonly<PublicUser>;

// 使用例
const updateData: UserUpdateInput = {
name: "山田次郎"
// すべてオプショナルなので、変更したいプロパティだけ指定
};

const publicUser: ReadonlyPublicUser = {
id: 1,
name: "山田太郎",
email: "yamada@example.com",
role: "user",
createdAt: new Date(),
updatedAt: new Date()
};
// publicUser.name = "変更"; // Error: 読み取り専用

試してみよう: 更新関数の型を設計しよう ★★

以下の要件を満たす型と関数を実装してください。

要件:

  1. Product型を定義(id, name, price, category, inStock)
  2. updateProduct関数を実装(更新したいプロパティだけを渡せる)
  3. getProductSummary関数を実装(nameとpriceだけを返す)
  4. ProductMap型を定義(カテゴリ名をキーとした商品配列のマップ)
// 以下を実装してください
// interface Product { ... }
// function updateProduct(...): Product
// function getProductSummary(...): ...
// type ProductMap = ...
ヒント
  1. Partial<T>で更新用の引数を定義
  2. Pick<T, K>で必要なプロパティだけを抽出
  3. Record<K, T>でカテゴリマップを定義
  4. スプレッド構文で更新を適用
回答と解説
// 商品の型を定義
interface Product {
id: number;
name: string;
price: number;
category: string;
inStock: boolean;
}

// 更新関数:Partial<Product>で一部のプロパティだけ更新可能に
// ただしidは変更不可にするためOmitで除外
function updateProduct(
product: Product,
updates: Partial<Omit<Product, "id">>
): Product {
return { ...product, ...updates };
}

// サマリー取得:Pick<Product, ...>で必要なプロパティだけを返す
type ProductSummary = Pick<Product, "name" | "price">;

function getProductSummary(product: Product): ProductSummary {
return {
name: product.name,
price: product.price
};
}

// カテゴリマップ:Record<string, Product[]>でカテゴリ別の商品配列
type ProductMap = Record<string, Product[]>;

// テスト
const laptop: Product = {
id: 1,
name: "ノートPC",
price: 120000,
category: "electronics",
inStock: true
};

// 価格と在庫を更新
const updatedLaptop = updateProduct(laptop, { price: 110000, inStock: false });
console.log(updatedLaptop);
// { id: 1, name: "ノートPC", price: 110000, category: "electronics", inStock: false }

// サマリーを取得
const summary = getProductSummary(laptop);
console.log(summary);
// { name: "ノートPC", price: 120000 }

// カテゴリマップを作成
const productsByCategory: ProductMap = {
electronics: [laptop],
books: [
{ id: 2, name: "TypeScript入門", price: 3000, category: "books", inStock: true }
]
};

console.log(productsByCategory.electronics);
// [{ id: 1, name: "ノートPC", ... }]

解説:

  • Partial<Omit<Product, "id">>:idを除外した上で、残りをオプショナルに
  • Pick<Product, "name" | "price">:必要なプロパティだけを抽出した型
  • Record<string, Product[]>:文字列キーと商品配列のマップ型

これらのユーティリティ型を組み合わせることで、型安全かつ柔軟なAPIを設計できます。

まとめ

この章で学んだこと:

  • Partial<T>: すべてのプロパティをオプショナルにする
  • Required<T>: すべてのプロパティを必須にする
  • Readonly<T>: すべてのプロパティを読み取り専用にする
  • Pick<T, K>: 指定したプロパティのみを抽出する
  • Omit<T, K>: 指定したプロパティを除外する
  • Record<K, T>: キーと値の型を指定してオブジェクト型を作成する
  • Exclude<T, U>: Union型から特定の型を除外する
  • Extract<T, U>: Union型から特定の型を抽出する
  • NonNullable<T>: null/undefinedを除外する
  • ReturnType<T>: 関数の戻り値の型を取得する
  • Parameters<T>: 関数の引数の型を取得する

次の章では、Mapped TypesとConditional Typesについて学び、独自のユーティリティ型を作成する方法を習得します。

初学者がつまずきやすいポイント

警告

よくある間違い

Partial<T>の結果がすべてundefinedを含むことを忘れる

interface User {
name: string;
age: number;
}

type PartialUser = Partial<User>;
// { name?: string; age?: number; }

function processUser(user: PartialUser): void {
// ❌ user.nameはstring | undefinedなので直接使用できない
// console.log(user.name.toUpperCase()); // Error

// ✅ undefinedチェックが必要
if (user.name !== undefined) {
console.log(user.name.toUpperCase());
}
}

原因: Partial<T>はすべてのプロパティをオプショナルにするため、値はundefinedを含みます。 解決策: プロパティにアクセスする前に、undefinedチェックを行いましょう。

❌ ReadonlyがShallow(浅い)であることを知らない

interface User {
name: string;
address: {
city: string;
};
}

const user: Readonly<User> = {
name: "太郎",
address: { city: "東京" }
};

// user.name = "次郎"; // Error: 読み取り専用

// ❌ ネストしたオブジェクトは変更可能!
user.address.city = "大阪"; // OK(エラーにならない)

原因: Readonly<T>は浅い(shallow)読み取り専用であり、ネストしたオブジェクトには適用されません。 解決策: 深い読み取り専用が必要な場合は、カスタムの再帰的な型を作成するか、ライブラリを使用しましょう。

❌ PickとOmitの第2引数にtypoがあっても気づかない

interface User {
id: number;
name: string;
email: string;
}

// ❌ TypeScriptはtypoを検出しない(存在しないキーでもエラーにならない場合がある)
// TypeScript 4.x以降は改善されていますが、注意が必要

// ✅ keyofで型安全に
type UserKeys = keyof User; // "id" | "name" | "email"
type PickedUser = Pick<User, "name" | "email">; // OK

原因: TypeScriptのバージョンによっては、存在しないキーを指定してもエラーにならないことがあります。 解決策: keyofを使って有効なキーを確認しつつ使用しましょう。

❌ ReturnTypeに関数自体を渡してしまう

function createUser() {
return { id: 1, name: "太郎" };
}

// ❌ 関数自体を渡すとエラー
// type UserType = ReturnType<createUser>; // Error

// ✅ typeofで関数の「型」を取得してから渡す
type UserType = ReturnType<typeof createUser>;
// { id: number; name: string; }

原因: ReturnTypeは「関数型」を受け取りますが、createUserは値です。 解決策: typeofで関数の型を取得してからReturnTypeに渡しましょう。

❌ Excludeをオブジェクト型に使おうとする

interface User {
id: number;
name: string;
password: string;
}

// ❌ ExcludeはUnion型用、オブジェクトのプロパティ除外には使えない
// type PublicUser = Exclude<User, "password">; // 意図通りに動かない

// ✅ オブジェクトのプロパティ除外にはOmitを使う
type PublicUser = Omit<User, "password">;
// { id: number; name: string; }

// Excludeの正しい使い方(Union型から除外)
type AllStatus = "pending" | "approved" | "rejected";
type ActiveStatus = Exclude<AllStatus, "rejected">;
// "pending" | "approved"

原因: ExcludeはUnion型から特定の型を除外するもので、オブジェクトのプロパティ除外には使えません。 解決策: オブジェクトのプロパティ除外にはOmit、Union型の除外にはExcludeを使いましょう。


次に読む