📦

TypeScriptのユーティリティ型を使いこなす:実践的なレシピ集🎯

に公開

こんにちはCloudCreatorLabを運営している中の人です!👋
今回はTypeScriptのユーティリティ型について、実践的な使い方を紹介していきます。
ユーティリティ型を活用することで、型安全性を高めながら、より効率的な開発が可能になります。

はじめに 🎬

TypeScriptの型システムは強力ですが、特にユーティリティ型を使いこなすことで、より効率的で型安全なコードを書くことができます。
過去に非常にきれいな形で他の方解説されている記事もありますので気になる方はご確認を‼
この記事では、基本的なユーティリティ型から応用まで、実際のユースケースとともに解説していきます!

ユーティリティ型とは? 🤔

ユーティリティ型は、既存の型を変換して新しい型を作るための組み込み型です。これらを活用することで:

  • 型の再利用性が高まる
  • コードの重複を減らせる
  • 型安全性が向上する
  • 開発効率が上がる

といったメリットが得られます。

基本ユーティリティ型の実践的な使い方 🎯

Partial<T> - 部分的な更新処理に便利! ✨

Partial<T>は、オブジェクトの一部のプロパティだけを更新したい場合に非常に便利です。

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

// ユーザー情報の更新時に使える
function updateUser(id: string, data: Partial<User>) {
  // 一部のプロパティだけ更新できる
  console.log(`ユーザー ${id} を更新:`, data);
}

// 使用例
updateUser('user-1', { name: '新しい名前' }); // OK
updateUser('user-2', { age: 25, email: 'new@example.com' }); // OK
updateUser('user-3', {}); // OK - 空のオブジェクトも許容

実践的なユースケース:

  • フォームの部分的な更新
  • APIリクエストのパラメータ
  • 設定の更新処理

Required<T> - オプショナルなプロパティを必須に! 🔒

Required<T>は、オプショナルなプロパティを必須に変換します。特に設定の初期化時などに有用です。

interface Config {
  apiKey?: string;
  endpoint?: string;
  timeout?: number;
}

// 設定が必須の関数
function initialize(config: Required<Config>) {
  // すべてのプロパティが必須なので、安全に使用可能
  console.log('API設定を初期化:', config);
}

// 使用例
initialize({
  apiKey: 'xxx',
  endpoint: 'https://api.example.com',
  timeout: 5000
}); // OK

// エラー: 必須プロパティが不足
initialize({
  apiKey: 'xxx'
}); // Error: Property 'endpoint' is missing

実践的なユースケース:

  • アプリケーションの初期化設定
  • 必須パラメータの強制
  • 型安全な設定管理

Readonly<T> - イミュータブルなオブジェクトを作る! 🛡️

Readonly<T>は、オブジェクトを不変(イミュータブル)にします。特に定数や設定値の保護に有用です。

interface Settings {
  theme: string;
  language: string;
  fontSize: number;
}

const defaultSettings: Readonly<Settings> = {
  theme: 'dark',
  language: 'ja',
  fontSize: 16
};

// エラー: 読み取り専用プロパティは変更できない
defaultSettings.theme = 'light'; // Error: Cannot assign to 'theme' because it is a read-only property

// 安全な使用方法
function getSettings(): Readonly<Settings> {
  return { ...defaultSettings };
}

実践的なユースケース:

  • 定数の定義
  • 設定値の保護
  • 状態管理での不変性の保証

Pick<T, K> / Omit<T, K> - 必要なプロパティだけを選ぶ! 🎯

Pick<T, K>Omit<T, K>は、型から必要なプロパティだけを選択したり、不要なプロパティを除外したりするのに便利です。

interface Product {
  id: string;
  name: string;
  price: number;
  description: string;
  stock: number;
  createdAt: Date;
  updatedAt: Date;
}

// 商品一覧表示用の型(必要な情報だけ)
type ProductListItem = Pick<Product, 'id' | 'name' | 'price'>;

// 商品詳細表示用の型(在庫情報を除外)
type ProductDetail = Omit<Product, 'stock'>;

// 商品作成用の型(IDとタイムスタンプを除外)
type CreateProduct = Omit<Product, 'id' | 'createdAt' | 'updatedAt'>;

実践的なユースケース:

  • ビュー層での型の最適化
  • APIリクエスト/レスポンスの型定義
  • フォームの入力型の定義

Record<K, T> - キーと値の型を定義する! 📝

Record<K, T>は、特定のキーと値の型を持つオブジェクト型を定義するのに便利です。

// 都道府県ごとの人口データ
type Prefecture = 'Tokyo' | 'Osaka' | 'Fukuoka';
type PopulationData = Record<Prefecture, number>;

const population: PopulationData = {
  Tokyo: 14000000,
  Osaka: 8800000,
  Fukuoka: 5100000
};

// エラー: 未定義の都道府県は使用できない
population['Hokkaido'] = 5200000; // Error: Property 'Hokkaido' does not exist

// 動的なキーを持つ場合
type DynamicRecord = Record<string, number>;
const scores: DynamicRecord = {
  'user1': 100,
  'user2': 200
};

実践的なユースケース:

  • 列挙型の値のマッピング
  • 動的なキーを持つオブジェクト
  • 設定値の型定義

一歩進んだユーティリティ型と実践例 🚀

ReturnType<F> / Parameters<F> - 関数の型情報を取得! 🔍

関数の戻り値型や引数の型を取得するのに便利です。特にAPIクライアントの型定義で重宝します。

async function fetchUser(id: string): Promise<User> {
  // APIからユーザー情報を取得
  return { name: 'Taro', age: 20, email: 'taro@example.com' };
}

// 戻り値の型を取得
type UserResponse = ReturnType<typeof fetchUser>; // Promise<User>

// 引数の型を取得
type FetchUserParams = Parameters<typeof fetchUser>; // [string]

// 実践的な使用例
async function handleUserFetch(...args: FetchUserParams) {
  const response: UserResponse = await fetchUser(...args);
  return response;
}

Awaited<P> - Promiseの解決後の型を取得! ⚡

Awaited<P>は、Promiseの解決後の型を取得するのに便利です。

type UserResponse = Awaited<ReturnType<typeof fetchUser>>; // User

// 実践的な使用例
async function processUser(id: string) {
  const user: UserResponse = await fetchUser(id);
  // userは既に解決済みの型なので、.then()などは不要
  console.log(user.name);
}

NonNullable<T> - null と undefined を除外! 🧹

NonNullable<T>は、型からnullとundefinedを除外します。特にフォームの入力値の処理で有用です。

type UserInput = string | null | undefined;
type ValidUserInput = NonNullable<UserInput>; // string

function validateInput(input: UserInput): ValidUserInput | null {
  if (input == null) return null;
  return input.trim();
}

発展:カスタムユーティリティ型の作成例 🎨

特定のキーだけオプショナルにする型 🎯

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

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

// nameとageだけオプショナル
type UpdateUserData = Optional<User, 'name' | 'age'>;

// 使用例
const updateData: UpdateUserData = {
  id: 'user-1',
  email: 'new@example.com',
  // nameとageはオプショナル
};

APIエンドポイントのパスとレスポンス型をマッピングする型 🌐

type ApiResponse<T> = {
  data: T;
  status: number;
  message: string;
};

type ApiEndpoints = {
  '/users': User[];
  '/users/:id': User;
  '/products': Product[];
};

type ApiResponseMap = {
  [K in keyof ApiEndpoints]: ApiResponse<ApiEndpoints[K]>;
};

// 使用例
const api: ApiResponseMap = {
  '/users': {
    data: [{ id: '1', name: 'Taro' }, { id: ~~ }, ...],
    status: 200,
    message: 'OK'
  },
  '/users/:id': {
    data: { id: '1', name: 'Taro' },
    status: 200,
    message: 'OK'
  }
  // '/products': { ... } // '/products' エンドポイントの定義もここに追加可能
};

まとめ 🎉

TypeScriptのユーティリティ型を活用することで、より型安全で効率的な開発が可能になります。
この記事で紹介したパターンを参考に、あなたのプロジェクトでもユーティリティ型を活用してみてください!
実際PrismaなどのORMで行うデータのやり取りでは非常にコスパ良く型チェックが行え意図したもののやり取りが可能になります。

主なメリット 💪

  1. 型の再利用性が向上
  2. コードの重複を削減
  3. 型安全性の確保
  4. 開発効率の向上
  5. メンテナンス性の向上

参考リンク 🔗

Discussion