TypeScriptでコードを書いていると、型定義が冗長になったり、重複が発生したりすることがよくあります。本記事では、型定義をリファクタリングして保守性を向上させるための実践的な手法を解説します。

共通の型をユーティリティ型で抽出する

複数の型で共通部分がある場合、その部分を抽出して再利用可能な型として定義することで、コードの重複を減らすことができます。

// 改善前
interface User {
  id: number;
  name: string;
  email: string;
}

interface Product {
  id: number;
  name: string;
  price: number;
}

// 改善後
interface BaseEntity {
  id: number;
  name: string;
}

interface User extends BaseEntity {
  email: string;
}

interface Product extends BaseEntity {
  price: number;
}

Union型の最適化

多くの型をUnionで結合する場合、メンテナンス性を考慮して整理することが重要です。

// 改善前
type Status = 'pending' | 'active' | 'inactive' | 'deleted' | 'archived';

// 改善後
const STATUS = {
  PENDING: 'pending',
  ACTIVE: 'active',
  INACTIVE: 'inactive',
  DELETED: 'deleted',
  ARCHIVED: 'archived',
} as const;

type Status = typeof STATUS[keyof typeof STATUS];

Partial型とPick型の活用

オブジェクトの一部のプロパティだけを使用する場合、Partial型やPick型を使用することで型定義をクリーンに保てます。

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

// 更新時に必要なフィールドだけを型定義
type UserUpdateParams = Partial<Pick<User, 'name' | 'email' | 'address'>>;

インデックスシグネチャの最適化

動的なプロパティを持つオブジェクトの型定義では、インデックスシグネチャを使用しますが、より具体的な型定義を心がけましょう。

// 改善前
interface Dictionary {
  [key: string]: any;
}

// 改善後
interface Dictionary<T> {
  [key: string]: T;
}

const numberDict: Dictionary<number> = {
  one: 1,
  two: 2,
};

型の条件分岐を活用する

特定の条件に基づいて型を変更したい場合は、条件付き型(Conditional Types)を使用します。

type IsString<T> = T extends string ? true : false;

// 使用例
type Result1 = IsString<string>; // true
type Result2 = IsString<number>; // false

まとめ

型定義のリファクタリングは、以下のポイントに注意して行いましょう:

  • 共通の型は基底型として抽出する
  • Union型は定数オブジェクトを活用して管理する
  • ユーティリティ型を適切に使用して型定義を簡潔にする
  • インデックスシグネチャはできるだけ具体的な型を指定する
  • 条件付き型を活用して柔軟な型定義を行う

これらの手法を適切に組み合わせることで、TypeScriptの型定義をより保守性の高いものにすることができます。