【実務・中級編】Interfaceの「合成」:コンポジション指向で再利用可能な型パーツを作る – TypeScript コア・型システムの基礎解析バイブル

巨大なInterfaceという「技術的負債」を殺せ:コンポジションで実現する堅牢な型設計

TypeScriptで開発をしていると、必ず直面する「肥大化したInterface」という怪物。
`interface User` が50行を超え、`UserForm`、`UserDetail`、`UserApiRequest` と似たような型が乱立し、修正のたびに全箇所を追う羽目になる。

多くのエンジニアはここで「継承(extends)」を使おうとします。しかし、安易な継承は型定義をカオスに陥れる元凶です。今日は、「継承ではなく合成(Composition)」という観点から、TypeScriptの型システムを再構築する方法を伝授します。

—

なぜ「継承」は罠なのか

継承は「is-a(〜である)」関係を定義しますが、フロントエンドのコンポーネント設計において、この関係はしばしば破綻します。

  • 脆弱な結合: 親の定義を変更すると、予期せぬ場所まで型エラーが伝播する。
  • 継承の深さ: 階層が深くなると、`tsc` が型の評価(Type Instantiation)を解決する際の計算コストが跳ね上がり、エディタの補完が重くなる。
  • 柔軟性の欠如: 複数の親を持つことができないため、機能が混ざり合うと「怪物のような親クラス」が生まれる。

これからは、「小さなパーツを作り、それを組み合わせる(Composition)」設計にシフトしましょう。

—

実践:コンポジションによる型設計パターン

APIレスポンスの管理を例に、具体的なコードを見ていきましょう。

1. 小さな型パーツ(Atomic Types)の定義

まずは、再利用性の高い最小単位の型を定義します。

// 識別子に関するパーツ
type IdIdentity = { id: string };
type Timestamp = { createdAt: string; updatedAt: string };

// ユーザー属性に関するパーツ
type UserProfile = { username: string; email: string };
type UserAuth = { role: ‘admin’ | ‘user’; isActive: boolean };

2. Intersection Types で合成する

これらを組み合わせて、ドメインに即した型を構築します。

// データベース上の完全なユーザー型
export type User = IdIdentity & UserProfile & UserAuth & Timestamp;

// APIリクエスト用(idやタイムスタンプは不要)
export type CreateUserRequest = UserProfile & { password: string };

// 特定の属性のみ必要なコンポーネント用
export type UserSummary = IdIdentity & UserProfile;

ここでのポイント:
`type` を使うことで、`&` (Intersection) による合成が自然に行えます。これにより、コンポーネントに必要なプロパティだけを抽出する「型レベルの合成」が可能になります。

—

現場で役立つ「高度な合成テクニック」

ユーティリティ型を用いた「差分」の抽出

単にパーツを合成するだけでなく、既存の型から不要なものを削ぎ落とすのも合成の作法です。

// 既存のUser型から、UI表示に不要な機密情報を除外して合成
export type UserDisplay = Omit & {
lastLogin: string;
};

インターフェースの「プロパティオーバーライド」

もしInterfaceを使う場合でも、継承を避けて `Pick` や `Omit` を活用しましょう。

interface BaseProps {
id: string;
className?: string;
}

// 継承せず、必要なものを合成する
interface ButtonProps extends BaseProps {
label: string;
onClick: () => void;
}

—

パフォーマンスと保守性のための注意点

1. 型評価のコストを意識する

複雑な `Conditional Types` や `Mapped Types` を多用しすぎると、TypeScriptコンパイラは膨大な計算を強いられます。

  • 名前付き型を優先: インラインで複雑な合成を行うと、エラーメッセージが `(Id & Profile) & (Auth & Timestamp)` のように地獄のような表示になります。必ず名前(`type …`)を付けてください。

2. 「合成の深さ」を制限する

3階層以上の合成は、型推論の可読性を著しく下げます。フラットな合成を心がけてください。

—

結論:型は「ドキュメント」である

TypeScriptの型システムは、単なるバリデーションツールではありません。「このデータの構造はどうあるべきか」という設計図です。

継承でガチガチに固めた巨大なInterfaceは、変更に対する拒絶反応を示します。しかし、小さなパーツを合成する設計は、要件の変化に対して「パーツを入れ替えるだけ」という柔軟性をもたらします。

今日から、あなたのプロジェクトの `types.ts` を開いてください。`extends` の文字を消し、`&` や `Pick/Omit` を使って型を「組み立てる」意識に変えるのです。その瞬間から、あなたのコードベースは圧倒的にメンテナンスしやすくなるはずです。

設計は、引き算から始まる。
最高のプロダクトには、常に研ぎ澄まされたシンプルな型が存在します。

タイトルとURLをコピーしました