【実務・中級編】 インターセクション型(Intersection Types)の定義と活用 – TypeScript実践ガイド

インターセクション型(&)は「合成の魔法」か、それとも「設計の落とし穴」か?

現場でTypeScriptを書いていると、最初は `interface` や `type` を素直に並べているだけで幸せになれる。しかし、プロジェクトが巨大化し、APIのレスポンスやコンポーネントのPropsが複雑に絡み合い始めると、途端に型定義は迷宮と化す。

そんな時、多くのエンジニアが手にする武器がインターセクション型(Intersection Types)だ。「`A & B` と書けば、両方のプロパティを併せ持った最強の型ができる」——そう信じているなら、少しだけ立ち止まって耳を貸してほしい。

インターセクション型は強力だが、その本質を理解せずに使うと、将来の自分を苦しめる「負の遺産」になりかねないからだ。

—

インターセクション型の基本と「合成」のリアル

インターセクション型は、一言で言えば「集合論的な積」だ。`TypeA & TypeB` は、TypeAが持つすべてのプロパティと、TypeBが持つすべてのプロパティを「どちらも満たすもの」として定義する。

ここで重要なのは、コンパイラがこれをどう扱っているかだ。TypeScriptは静的解析の段階で、これらの型を一つの大きな「匿名オブジェクト型」としてマージする。

type UserBase = { id: string; name: string; };
type UserProfile = { bio: string; avatarUrl: string; };

// 合成の基本形
type User = UserBase & UserProfile;

const user: User = {
id: “u123”,
name: “Alice”,
bio: “Frontend Specialist”,
avatarUrl: “https://example.com/alice.png”
};

ここまでは教科書通りだ。しかし、実務で本当に恐ろしいのは「プロパティの衝突」である。

「衝突」が起きたとき、TypeScriptはどう裁くのか?

もし `UserBase` と `UserProfile` の両方に `id` があったらどうなるか?

type A = { id: string; };
type B = { id: number; };

type C = A & B;
// Cの id は string & number となり、結果的に “never” になる。

`string` であり、かつ `number` である値などこの世には存在しない。だから `never` になる。ブラウザ(JavaScriptのランタイム)が実行時にどうこうする以前に、TypeScriptのコンパイラが「そんな矛盾したオブジェクトは作らせない」と門前払いを食らわせるわけだ。

現場でよくあるミスは、外部ライブラリの型定義をインターセクションで強引に拡張しようとして、この `never` 地獄にハマるパターンだ。「あ、型が合わない」と思って `any` を逃げ道に使うくらいなら、`Omit` で既存の型を間引く勇気を持つべきだ。

実務で使える「賢い合成」のパターン

ただインターセクションを並べるのではなく、実務では以下のように「既存の型をベースに差分を足す」というアプローチがスマートだ。

type BaseProps = {
id: string;
className?: string;
};

// BasePropsを継承しつつ、特定のプロパティを上書き、あるいは追加する
// PickやOmitと組み合わせるのがシニアの流儀
type ButtonProps = BaseProps & {
onClick: () => void;
variant: ‘primary’ | ‘secondary’;
};

// もしidをnumberに強制変更したい場合は、一度間引いてから合成する
type NumericIdProps = Omit & {
id: number;
};

なぜ「インターセクション」なのか?

`interface` の `extends` と何が違うのか?とよく聞かれる。
結論から言えば、「名前のない型(匿名型)を即席で作りたいとき」にこそインターセクションが輝く。

  • `interface`: 拡張性が高く、エラーメッセージも明快。大規模な設計に適している。
  • `type &`: 合成が容易で、柔軟性が高い。関数の中のローカルな型定義や、複雑なユーティリティ型を作る際に真価を発揮する。

特に、Reactのコンポーネントにおける「Propsの合成」では、インターセクション型を多用することになるだろう。

—

シニアからのアドバイス:深入りしすぎないこと

インターセクション型は、複雑にすればするほど、エディタ上での「ホバーした時の型表示」が読めなくなる。`TypeA & TypeB & TypeC & …` と連結しすぎると、TypeScriptの型推論のパフォーマンスも落ちるし、何よりコードを読んでいる人間が「結局、最終的に必要なプロパティは何なのか?」を把握できなくなる。

「合成できるからといって、すべてを合成する必要はない」

これが、現場を生き抜くための教訓だ。型定義が長くなりすぎたら、それは「その型が責務を持ちすぎている」というサインかもしれない。そんな時は、一度立ち止まってコンポーネントやドメインの境界を見直してみてほしい。

TypeScriptは、あなたの書いたコードを安全にするためのツールだ。ツールに振り回されるのではなく、ツールを「可読性の向上」という目的のために使いこなす。それができるようになった時、君はもう一段上のフロントエンド・スペシャリストになれるはずだ。

次は、`Pick` や `Partial` などのユーティリティ型とインターセクションを組み合わせて、さらに洗練された型システムを構築する方法について話そうか。また現場で会おう。

コメント

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