🐙

【TypeScript】もう型定義で消耗しない!便利なUtility Types

に公開

はじめに

TypeScriptには、そんな悩みを解決してくれる便利なUtility Types(ユーティリティ型)という機能が組み込まれています。これらは、まるで「型の便利ツール箱」のようなもので、既存の型を元にして新しい型を簡単に、そして安全に作り出すことができます。

改めてドキュメントを確認しつつ、AIも用いて忘備録としてまとめました。

1. Partial<Type> - プロパティを全部「あってもなくても良い」にする魔法 ✨

🤔 これは何?

Partial<Type>は、指定したType(例えばインターフェースや型エイリアス)の すべてのプロパティをオプショナル(?が付いた状態) に変身させます。つまり、「このプロパティはあってもなくても良いよ」という状態の型を作ることができます。

💡 どんな時に便利?

  • 一部のデータだけ更新したい時: ユーザー設定画面で、名前だけ変更したり、メールアドレスだけ変更したりする場合、変更するフィールドだけを持つオブジェクトを送りたいですよね。そんな時にPartialが役立ちます。
  • デフォルトオプション: 関数の引数で、いくつかのオプション設定を受け取りたいけど、ユーザーが指定しなくても良いようにしたい場合など。

✍️ コード例

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

// Userのプロパティが全部オプショナルになった型
type UserProfileUpdate = Partial<User>;
// UserProfileUpdate はこんな感じになります:
// {
//   id?: number;
//   name?: string;
//   email?: string;
//   age?: number;
// }

function updateUserProfile(userId: number, updates: UserProfileUpdate) {
  // ... ユーザーデータを更新する処理 ...
  console.log(`Updating user ${userId} with:`, updates);
}

// 名前だけ更新する
updateUserProfile(1, { name: "新しい名前" });
// メールアドレスと年齢を更新する
updateUserProfile(2, { email: "new@example.com", age: 31 });

2. Required<Type> - 全部「必須」にする逆転魔法 🪄

🤔 これは何?

Required<Type>は、Partialとは逆に、指定したTypeすべてのプロパティを必須(オプショナル?を取り除く) にします。元々オプショナルだったプロパティも、必須のプロパティに変わります。

💡 どんな時に便利?

  • フォーム入力完了時: フォーム入力中はオプショナルな項目があっても、送信時にはすべての項目が入力されていることを保証したい、といった場合。
  • データ登録前: APIから受け取ったデータには欠けている項目があるかもしれないけど、データベースに保存する前にはすべての必須項目が揃っている型として扱いたい場合。

✍️ コード例

config.ts
interface AppConfig {
  appName?: string; // デフォルト値があるかもしれないのでオプショナル
  version?: string; // 同上
  apiKey: string;  // これは必須
}

// AppConfigのプロパティが全部必須になった型
type FinalAppConfig = Required<AppConfig>;
// FinalAppConfig はこんな感じになります:
// {
//   appName: string; // 必須になる!
//   version: string; // 必須になる!
//   apiKey: string;
// }

function initializeApp(config: FinalAppConfig) {
  // ここでは config の全てのプロパティが必ず存在するものとして扱える
  console.log(`Initializing ${config.appName} v${config.version}...`);
  // console.log(config.apiKey); // apiKeyも使える
}

const finalConfig: FinalAppConfig = {
  appName: "My Awesome App",
  version: "1.0.0",
  apiKey: "xyz123abc",
};

initializeApp(finalConfig);

// ↓ これはエラーになる (versionがないため)
// const incompleteConfig: FinalAppConfig = {
//   appName: "Incomplete App",
//   apiKey: "def456ghi",
// };

3. Readonly<Type> - 変更禁止!カチコチにする盾 🛡️

🤔 これは何?

Readonly<Type>は、指定したTypeすべてのプロパティを読み取り専用(readonly にします。これにより、オブジェクトが作られた後に、そのプロパティの値を変更しようとすると、TypeScriptがエラーを出してくれるようになります。

💡 どんな時に便利?

  • 設定オブジェクト: アプリケーションの設定情報など、一度読み込んだら変更されたくないデータを安全に保ちたい場合。
  • 状態管理: ReduxやVuexのような状態管理ライブラリで、状態(State)が意図せず変更されるのを防ぎたい場合。
  • 不変性(Immutability)の確保: 関数の引数で受け取ったオブジェクトを関数内で変更しないことを保証したい場合。

✍️ コード例

settings.ts
interface Settings {
  theme: "light" | "dark";
  fontSize: number;
}

// Settingsのプロパティが読み取り専用になった型
const appSettings: Readonly<Settings> = {
  theme: "dark",
  fontSize: 14,
};

console.log(appSettings.theme); // "dark" (読み取りはOK)

// ↓ これはエラーになる! Readonlyなので変更できない
// appSettings.theme = "light";
// Error: Cannot assign to 'theme' because it is a read-only property.

4. Pick<Type, Keys> - 好きなものだけ選抜!つまみ食いツール 🤏

🤔 これは何?

Pick<Type, Keys>は、指定したTypeの中から、Keysで指定したプロパティだけを選び出して 新しい型を作ります。「Typeの中から、Key1とKey2だけちょうだい!」という感じです。

💡 どんな時に便利?

  • リスト表示用のデータ: ユーザー一覧を表示する時、全情報ではなくidnameだけが必要な場合。
  • APIレスポンスの一部利用: APIから返ってきた巨大なオブジェクトから、必要な情報だけを取り出して使いたい場合。
  • コンポーネントのProps定義: 大きなデータ型の一部だけをコンポーネントのPropsとして渡したい場合。

✍️ コード例

product.ts
interface Product {
  id: string;
  name: string;
  price: number;
  description: string;
  inStock: boolean;
}

// Product から 'id' と 'name' だけを選んだ型
type ProductSummary = Pick<Product, "id" | "name">;
// ProductSummary はこんな感じ:
// {
//   id: string;
//   name: string;
// }

const products: ProductSummary[] = [
  { id: "p1", name: "すごいガジェット" },
  { id: "p2", name: "便利なツール" },
];

function displayProductList(productList: ProductSummary[]) {
  productList.forEach(product => {
    console.log(`ID: ${product.id}, Name: ${product.name}`);
    // console.log(product.price); // Error: Property 'price' does not exist on type 'ProductSummary'.
  });
}

displayProductList(products);

5. Omit<Type, Keys> - これはいらない!除外ツール 🗑️

🤔 これは何?

Omit<Type, Keys>は、Pickの逆で、指定したTypeの中から、Keysで指定したプロパティを除外して 新しい型を作ります。「Typeの中から、Key1とKey2以外を全部ちょうだい!」という感じです。

💡 どんな時に便利?

  • 機密情報の除去: ユーザーオブジェクトからパスワード情報などを除外して、フロントエンドに送るデータを作りたい場合。
  • 内部実装の隠蔽: 外部に公開するAPIの型から、内部でだけ使っているプロパティを除外したい場合。
  • フォームデータの整形: 送信するデータから、createdAtupdatedAtのような自動で設定される項目を除きたい場合。

✍️ コード例

user.ts
interface User {
  id: number;
  name: string;
  email: string;
  passwordHash: string; // これは外部に見せたくない
  createdAt: Date;
}

// User から 'passwordHash' と 'createdAt' を除外した型
type PublicUser = Omit<User, "passwordHash" | "createdAt">;
// PublicUser はこんな感じ:
// {
//   id: number;
//   name: string;
//   email: string;
// }

function sendPublicUserData(user: PublicUser) {
  console.log("Sending user data to client:", user);
  // console.log(user.passwordHash); // Error: Property 'passwordHash' does not exist on type 'PublicUser'.
}

const userData: PublicUser = {
    id: 1,
    name: "Public Taro",
    email: "public@example.com"
}
sendPublicUserData(userData);

6. Record<Keys, Type> - キーと値のペアで辞書を作る 📖

🤔 これは何?

Record<Keys, Type>は、キーがKeys型、値がType型となるようなオブジェクト型 を作ります。Keysには通常、'a' | 'b'のような文字列リテラルのユニオン型や、stringnumberを指定します。まるで、キーと値のペアが決まった「辞書」を作るようなイメージです。

💡 どんな時に便利?

  • 設定情報の管理: 機能フラグ({ featureA: boolean; featureB: boolean; })のように、キーが決まっていて値が同じ型のオブジェクト。
  • 多言語対応: 'ja''en'をキーとして、各言語の翻訳文字列を値に持つオブジェクト(Record<'ja' | 'en', string>)。
  • 状態マッピング: loading, success, error などの状態名をキーとして、対応するUIコンポーネントやメッセージを値に持つオブジェクト。

✍️ コード例

featureFlags.ts
// 機能名をキーとするユニオン型
type Feature = "darkMode" | "betaFeature" | "newLayout";

// 機能フラグ (キーがFeatureで、値がboolean)
const featureFlags: Record<Feature, boolean> = {
  darkMode: true,
  betaFeature: false,
  newLayout: true,
  // typoFeature: false, // Error: Object literal may only specify known properties...
};

if (featureFlags.darkMode) {
  console.log("ダークモードが有効です");
}

// CSSのスタイル定義などにも使える
type CSSProperties = Record<string, string | number>;
const styles: CSSProperties = {
    color: "blue",
    fontSize: 16,
    // isActive: true // Error: Type 'boolean' is not assignable to type 'string | number'.
}

7. Exclude<UnionType, ExcludedMembers> - ユニオン型から一部を除外 ✂️

🤔 これは何?

Exclude<UnionType, ExcludedMembers>は、UnionType(例: string | number | boolean)という合体型(ユニオン型)の中から、ExcludedMembersで指定した型を取り除いた 新しいユニオン型を作ります。

💡 どんな時に便利?

  • 型の絞り込み: APIから返ってくる値がstring | number | nullだけど、nullの場合はエラー処理するので、それ以外のstring | numberだけを扱いたい場合。
  • 特定の型を除外: イベントの種類を表すユニオン型から、特定のイベント(例: MouseEvent | KeyboardEvent | FocusEventからFocusEventを除外)を除いた型を作りたい場合。

✍️ コード例

filterTypes.ts
type ApiResponse = string | number | boolean | null;

// ApiResponse から null を除外した型
type ValidData = Exclude<ApiResponse, null>; // string | number | boolean

function processData(data: ValidData) {
  // ここでは data が null である可能性を考慮しなくて良い
  console.log("Processing data:", data);
}

// processData(null); // Error: Argument of type 'null' is not assignable to parameter of type 'string | number | boolean'.

type Events = "click" | "mouseover" | "keydown" | "focus";
// Events から "focus" を除外
type NonFocusEvents = Exclude<Events, "focus">; // "click" | "mouseover" | "keydown"

8. Extract<Type, Union> - ユニオン型から一部を抽出 🔍

🤔 これは何?

Extract<Type, Union>は、Excludeの逆で、Type(ユニオン型)の中から、Unionで指定した型だけを取り出して 新しいユニオン型を作ります。

💡 どんな時に便利?

  • 特定の型だけ処理したい: string | number | booleanの中から、stringまたはnumberの場合だけ処理する関数に渡す型を作りたい場合。
  • 共通の型を抽出: 複数のユニオン型に共通して含まれる型だけを取り出したい場合。

✍️ コード例

filterTypes.ts
type MixedData = string | number | boolean | { type: 'A' } | { type: 'B' };

// MixedData から string または number だけを抽出した型
type StringOrNumber = Extract<MixedData, string | number>; // string | number

function formatValue(value: StringOrNumber) {
  // ここでは value が string か number であることが保証される
  if (typeof value === 'string') {
    return value.toUpperCase();
  }
  return value.toFixed(2);
}

console.log(formatValue("hello")); // "HELLO"
console.log(formatValue(12.345)); // "12.35"
// formatValue(true); // Error: Argument of type 'boolean' is not assignable to type 'string | number'.

// MixedData からオブジェクト型だけを抽出
type ObjectTypes = Extract<MixedData, object>; // { type: 'A' } | { type: 'B' }

9. NonNullable<Type> - nullundefined を許さない!🚫

🤔 これは何?

NonNullable<Type>は、指定したTypeから nullundefined を取り除いた 新しい型を作ります。

💡 どんな時に便利?

  • 値が存在することの保証: オプショナルチェーン(?.)やNull合体演算子(??)を使った後など、その変数にはもうnullundefinedが入っていないことが分かっている場合に、そのことを型で明示したい時。
  • 外部ライブラリの型: ライブラリの型定義が Type | null | undefined となっているが、自分のコードの文脈では nullundefined がありえないことを保証したい場合。

✍️ コード例

nonnull.ts
type MaybeString = string | null | undefined;

function processString(str: NonNullable<MaybeString>) {
  // ここでは str が string 型であることが保証される
  console.log(str.toUpperCase());
}

let userInput: MaybeString = null;
// ... 何らかの処理で userInput に値が入る ...
userInput = "  hello world  ";

if (userInput !== null && userInput !== undefined) {
  // このブロック内では userInput は null でも undefined でもないことが確定している
  // なので NonNullable<MaybeString> (つまり string) 型として扱える
  processString(userInput.trim()); // OK!
}

// processString(null); // Error: Argument of type 'null' is not assignable to type 'string'.

10. Parameters<Type> - 関数の引数の型を取り出す 📥

🤔 これは何?

Parameters<Type>は、関数型Typeの引数(パラメータ)の型を、タプル型(型の配列みたいなもの)として 取り出します。

💡 どんな時に便利?

  • 関数のラップ: ある関数をラップ(包み込む)する別の関数を作るとき、元の関数と同じ引数を受け取りたい場合。
  • 型安全なイベント発行: イベント名とそのイベントが受け取る引数の型を定義しておき、イベントを発行する際に正しい引数が渡されているかチェックしたい場合。
  • 引数のロギング: 関数の引数をログに出力する共通関数を作りたい場合。

✍️ コード例

funcParams.ts
function greet(name: string, age: number) {
  console.log(`こんにちは、${name}さん (${age}歳)`);
}

// greet 関数の引数の型 [string, number] を取得
type GreetParams = Parameters<typeof greet>; // [string, number]

// GreetParams を使って、引数をログ出力する関数を作る
function logArguments(args: GreetParams) {
  console.log("Arguments:", args);
}

logArguments(["太郎", 30]); // Arguments: [ '太郎', 30 ]
// logArguments(["花子"]); // Error: Tuple type '[name: string, age: number]' of length '2' has no element at index '1'.

11. ReturnType<Type> - 関数の戻り値の型を取り出す 📤

🤔 これは何?

ReturnType<Type>は、関数型Typeの戻り値(リターン)の型を 取り出します。

💡 どんな時に便利?

  • 関数の結果を使う: ある関数の戻り値の型を、別の変数の型として使いたい場合。
  • 非同期処理の結果: Promiseを返す関数の Awaited<ReturnType<typeof func>> のように組み合わせて、非同期処理が完了した後の値の型を取得したい場合(後述のAwaitedも参照)。
  • 関数の組み合わせ: ある関数の戻り値を、別の関数の引数として渡すような処理の型チェックをしたい場合。

✍️ コード例

funcReturn.ts
function getUserData(userId: number): { id: number; name: string; isActive: boolean } {
  // ... ユーザーデータを取得する処理 ...
  return { id: userId, name: "ユーザー" + userId, isActive: true };
}

// getUserData 関数の戻り値の型を取得
type UserData = ReturnType<typeof getUserData>;
// UserData は { id: number; name: string; isActive: boolean } と同じ

let currentUser: UserData;

currentUser = getUserData(1);
console.log(currentUser.name); // "ユーザー1"
// console.log(currentUser.email); // Error: Property 'email' does not exist on type '{ id: number; name: string; isActive: boolean; }'.

12. InstanceType<Type> - クラスのインスタンスの型を取り出す 🏭

🤔 これは何?

InstanceType<Type>は、クラス(コンストラクタ関数)Typeから生成されるインスタンスの型を 取り出します。

💡 どんな時に便利?

  • ファクトリー関数: クラスのインスタンスを生成する関数(ファクトリー関数)を作る際、その関数の戻り値の型として指定したい場合。
  • クラスの型情報: クラスのインスタンスが持つプロパティやメソッドの型情報だけを参照したい場合。

✍️ コード例

classInstance.ts
class Car {
  brand: string;
  speed: number = 0;

  constructor(brand: string) {
    this.brand = brand;
  }

  accelerate(amount: number) {
    this.speed += amount;
  }
}

// Car クラスのインスタンスの型を取得
type CarInstance = InstanceType<typeof Car>;
// CarInstance は Car クラスのインスタンスと同じ型を持つ
// { brand: string; speed: number; accelerate: (amount: number) => void; }

// Car インスタンスを生成するファクトリー関数
function createCar(brand: string): CarInstance {
  return new Car(brand);
}

const myCar: CarInstance = createCar("Toyota");
myCar.accelerate(50);
console.log(myCar.speed); // 50
// console.log(myCar.color); // Error: Property 'color' does not exist on type 'Car'.

13. Awaited<Type> - Promiseの中身を取り出す 🎁

🤔 これは何?

Awaited<Type>は、Promise(非同期処理の結果を表すオブジェクト)が解決(成功)した時に得られる値の型 を取り出します。Promise<Promise<string>> のようにPromiseがネストしていても、最終的に得られる string 型を取り出してくれます。

💡 どんな時に便利?

  • 非同期APIのレスポンス型: Workspace などでAPIを呼び出した結果(Promise)から、実際に受け取るデータの型を知りたい場合。
  • async/await との連携: await を使ってPromiseから値を取り出した後の変数の型を定義したい場合。

✍️ コード例

asyncAwait.ts
async function fetchUserName(userId: number): Promise<string> {
  // ... サーバーからユーザー名を取得する非同期処理 ...
  return new Promise(resolve => setTimeout(() => resolve(`User_${userId}`), 100));
}

// fetchUserName が返す Promise<string> の中身の型 (string) を取得
type UserName = Awaited<ReturnType<typeof fetchUserName>>; // string
// ReturnType<typeof fetchUserName> は Promise<string>
// Awaited<Promise<string>> は string

async function displayUserName(userId: number) {
  const userName: UserName = await fetchUserName(userId); // awaitでPromiseの中身を取り出す
  // ここでは userName は string 型として扱える
  console.log(userName.toUpperCase());
}

displayUserName(123); // 少し待ってから "USER_123" と表示される

// ネストしたPromiseもOK
type NestedPromise = Promise<Promise<number>>;
type ResolvedNumber = Awaited<NestedPromise>; // number

14. 文字列操作ユーティリティ型 - 型レベルで文字を加工 ✍️

🤔 これは何?

  • Uppercase<StringType>: 文字列を全部大文字にする。
  • Lowercase<StringType>: 文字列を全部小文字にする。
  • Capitalize<StringType>: 文字列の先頭だけ大文字にする。
  • Uncapitalize<StringType>: 文字列の先頭だけ小文字にする。

これらは、文字列リテラル型(例: 'hello')に対して使います。

💡 どんな時に便利?

  • イベント名の統一: 'clickButton' のようなイベント名を 'ClickButton' のように統一した型を作りたい場合。
  • 定数名の生成: 'user' という型から 'USER_ID''userName' のような定数名を型レベルで作りたい場合。
  • テンプレートリテラル型との連携: 特定のフォーマットを持つ文字列リテラル型を生成したい場合。

✍️ コード例

stringManipulation.ts
type EventType = "click" | "mouseover" | "submit";

// イベントタイプをキャピタライズ (先頭大文字) した型
type CapitalizedEvent = Capitalize<EventType>; // "Click" | "Mouseover" | "Submit"

// CSSクラス名などに
type BoxType = "small" | "medium" | "large";
type BoxClass = `box-${BoxType}`; // "box-small" | "box-medium" | "box-large"

// イベントハンドラ名の生成
type HandlerName<T extends string> = `handle${Capitalize<T>}`;
type ClickHandlerName = HandlerName<"click">; // "handleClick"
type SubmitHandlerName = HandlerName<"submit">; // "handleSubmit"

const handlerName: ClickHandlerName = "handleClick";
// const handlerName2: ClickHandlerName = "handleclick"; // Error!

まとめ

今回は、TypeScriptの便利なUtility Typesの中から、特によく使われるものをピックアップしてご紹介しました。

  • Partial: 全部オプショナルに
  • Required: 全部必須に
  • Readonly: 全部読み取り専用に
  • Pick: 必要なものだけ選抜
  • Omit: 不要なものを除外
  • Record: 辞書型の作成
  • Exclude: ユニオン型から除外
  • Extract: ユニオン型から抽出
  • NonNullable: nullundefinedを除外
  • Parameters: 関数の引数の型を取得
  • ReturnType: 関数の戻り値の型を取得
  • InstanceType: クラスのインスタンスの型を取得
  • Awaited: Promiseの中身の型を取得
  • 文字列操作型 (Uppercase など): 文字列リテラルを加工

Discussion