TypeScriptの型演算が喰らう「見えないコスト」:Interface継承と交差型の深淵
大規模なモノレポや複雑なドメインモデルを抱えるプロジェクトにおいて、コンパイル速度の低下は単なる待ち時間ではない。それは、TypeScriptコンパイラ(`tsc`)の型チェッカーが、あなたの記述した型を「どう評価し、どこにキャッシュし、どの時点でギブアップするか」という、メモリとCPUの限界との戦いを意味している。
今日は、多くのエンジニアが「なんとなく」使い分けている `interface` の継承と `type` の交差型(Intersection Types)の背後にある、コンパイラ内部の挙動とメモリ消費の真実を暴く。
—
1. コンパイラの内部構造:なぜ「型」は重くなるのか
TypeScriptの型チェックは、単なるテキスト解析ではない。`tsc` は型を依存グラフとして管理し、型が参照されるたびに再帰的な評価を行う。
- `interface` の継承: 内部的には「フラットなオブジェクト型」のキャッシュを生成しやすい。ベースとなる型が確定していれば、派生型はプロパティの単なる追加として扱われるため、コンパイラの評価コストは非常に低い。
- 交差型 (`&`): これは「型を評価する」という演算を明示的に強制する。`A & B` を評価する際、コンパイラは `A` と `B` の全プロパティをマージし、競合を解決し、さらにそれらがプリミティブであれば矛盾がないかをチェックする。
比較:内部評価の負荷
// パターンA: 宣言的継承 (低負荷)
interface Base { id: string; }
interface User extends Base { name: string; }
// パターンB: 交差型 (高負荷)
type User = { id: string } & { name: string };
大規模プロジェクトで数百の型が交差型で連結されると、コンパイラは「型を評価する関数」を無限に再帰実行する状況に追い込まれる。これが、`tsc` が突然CPUファンを唸らせ、メモリを数GB喰らい尽くす原因だ。
—
2. メモリ消費のメカニズム:`instantiation` の深淵
TypeScriptには “Type Instantiation” (型のインスタンス化) という概念がある。複雑なジェネリクスや交差型を多用すると、コンパイラは同一の型を何度も再計算し、その結果をメモリ上にキャッシュしようとする。
しかし、メモリが枯渇するとキャッシュはパージされ、再び再計算が走る。この「再計算の無限ループ」こそが、大規模フロントエンド開発におけるコンパイル速度のボトルネックだ。
悪魔の例:交差型の連鎖
type A = { a: number };
type B = { b: number };
type C = { c: number };
// これが100個連なるとどうなるか?
type DeepChain = A & B & C & … & Z;
コンパイラは `DeepChain` を参照するたびに、この巨大な交差型のプロパティを深層まで探索し、マージ結果を計算する。これがコンパイラの「イベントループ」を塞ぎ、型チェック処理をシリアライズ(直列化)させてしまう。
—
3. 実践:最適化のための型設計戦略
極限のパフォーマンスを求めるのであれば、以下の原則を徹底すべきだ。
原則1:インターフェースによる「評価の遅延」
`interface` は名前付きの型としてコンパイラに認識されるため、`type` に比べてキャッシュ効率が良い。特に複雑なオブジェクト構造を定義する場合は、交差型ではなく `interface` の継承を使うべきである。
原則2:交差型の「フラット化」
やむを得ず `type` を使う場合、交差型を多用するのではなく、あらかじめ必要なプロパティを統合した型を定義する方が、コンパイラにとっては「計算済み」の型として扱えるため、圧倒的に軽い。
// 悪い例: 評価の度に再計算が発生
type Profile = Base & Settings & Preferences & Auth;
// 良い例: 事前に定義し、コンパイラの負荷を軽減
interface Profile extends Base, Settings, Preferences, Auth {}
原則3:再帰的な型を避ける
`Utility Types` でよく見る再帰的な型(`DeepPartial` など)は、型チェックの深さが深くなるほど、コンパイラのスタックを消費する。大規模プロジェクトでは、再帰の深さを制限するか、再帰を排除したフラットな型定義を優先せよ。
—
結論:型システムは「静的な構造」であれ
TypeScriptは、型を書けば書くほど、コンパイラという名の「並列計算機」を酷使することになる。
- `interface`: コンパイラのキャッシュフレンドリーな「静的構造」。
- `type` (交差型): コンパイラに計算を強いる「動的演算」。
シニアエンジニアとして設計を行う際、型定義を単なるデータの構造記述と捉えてはならない。それは、コンパイラが実行する「型アルゴリズムのソースコード」である。その計算量を最小化し、コンパイラのキャッシュ効率を最大化すること。それこそが、大規模プロジェクトのビルド時間を秒単位で短縮し、開発体験を極限まで高める唯一の解だ。
型を定義する前に、心の中で問いかけよ。
「この型は、コンパイラにとってどれほどの計算負荷になるか?」
その問いの先にのみ、真のTypeScriptマスターへの道がある。