こんにちは。TypeScriptという言語の深淵に触れようとしているあなたへ。
TypeScriptを書いていると、最初は「とりあえず全てを網羅した巨大なインターフェース」を作りたくなりますよね。しかし、それは後々「変更に弱いコード」という負債に変わります。
今日は、TypeScriptにおける「合成(Composition)」の極意を伝授します。継承(`extends`)に頼りすぎず、小さな型パーツをレゴブロックのように組み合わせて、しなやかな型システムを構築する方法を見ていきましょう。
—
1. なぜ「巨大なインターフェース」は悪なのか?
例えば、ユーザー情報を扱うシステムを想像してみてください。
// 悪い例:全てを詰め込んだ巨大インターフェース
interface User {
id: string;
name: string;
email: string;
address: string;
phoneNumber: string;
isAdmin: boolean;
lastLogin: Date;
}
このインターフェースを使っていると、例えば「ユーザーの名前とIDだけが必要な関数」を書く時にも、強制的に他のすべてのプロパティまで意識させられます。テストデータを作るのも一苦労ですよね。
「必要なものは必要な分だけ」。これが型設計の鉄則です。
—
2. 合成による型パーツの切り出し
巨大なものを分割するには、機能を「最小単位」にまで分解します。これがコンポジション指向の第一歩です。
// 1. 識別子パーツ
interface Entity {
id: string;
}
// 2. 基本情報パーツ
interface Profile {
name: string;
email: string;
}
// 3. 管理者権限パーツ
interface AdminRole {
isAdmin: boolean;
}
// これらを「合成」して必要な型を作る
type User = Entity & Profile;
type AdminUser = Entity & Profile & AdminRole;
このように、`&`(交差型)を使うことで、既存の型を壊さずに新しい型を定義できます。継承の `interface User extends Entity` も可能ですが、合成(Intersection Type)は「柔軟性」において勝ります。
—
3. なぜ「継承」よりも「合成」が優れているのか?
初学者が陥りやすいのが「なんでもかんでも `extends` で繋ごうとする」罠です。
- 継承 (`extends`): 「AはBである」という強固な親子関係を強制します。深い継承階層は、親の変更がすべての子に伝播し、コードの複雑性を爆発させます。
- 合成 (`&` や `Pick`, `Omit`): 「AはBとCの機能を持つ」という関係性です。機能単位で切り離されているため、不要なプロパティを混ぜずに、いつでも好きなパーツを組み合わせられます。
陥りやすいエラー:型推論とプロパティの衝突
合成する際、もし同じ名前のプロパティを異なる型で定義して結合すると、TypeScriptは「Never型」を返そうとします。これは「型システムが矛盾を検知している」証拠です。
interface A { prop: string }
interface B { prop: number }
type C = A & B;
// 結論:C.prop は string かつ number になる必要があるが、それは不可能。
// よって C.prop は ‘never’ になる。
このエラーが出たら、「あ、設計が少し強引だったかな?」と立ち止まってパーツを見直すサインです。
—
4. 現場で使える「型パーツの魔法」
TypeScriptには、合成を強力にするための「ユーティリティ型」が備わっています。これを使えば、既存の型から必要なものだけを抽出(ピック)したり、除外(オミット)したりできます。
interface User {
id: string;
name: string;
email: string;
isAdmin: boolean;
}
// 特定のパーツだけを抽出して新しい型を作る
type UserPreview = Pick
// 特定のパーツを除外して新しい型を作る
type UserWithoutAdmin = Omit
// 活用例:関数に渡すときは「必要な分だけ」を型にする
function printUserName(user: UserPreview) {
console.log(user.name);
}
このように、「元の型を加工して、目的の型を導き出す」のが、プロのTypeScriptエンジニアの流儀です。
—
まとめ:ここをクリアすれば、あなたはもう中級者
1. 巨大なインターフェースを作らない: 機能を最小単位(粒度)に分解する。
2. `extends` よりも `&` やユーティリティ型を活用する: 継承の縛りを避け、柔軟な合成を目指す。
3. 型は「制約」ではなく「設計図」: 型を書くことは、データの構造を整理することだと理解する。
TypeScriptの型システムは、単なるエラーチェックの道具ではありません。あなたの脳内にある複雑なロジックを整理し、チーム全体で共有するための「強力な地図」です。
今日学んだ「小さなパーツを組み合わせる」という視点さえあれば、どんなに巨大なプロジェクトでも、怖がることはありません。さあ、次はどんなパーツを組み合わせてみますか?
それでは、また次のコードの深淵でお会いしましょう。Happy Coding!