【テクニカル・上級編】Interfaceの「継承」と「交差型」:どちらがコンパイル速度に影響するか – TypeScript コア・型システムの基礎解析バイブル

TypeScriptの深淵:Interface継承と交差型(&)がコンパイルのボトルネックとなる理由

TypeScriptの型システムは、その柔軟性の代償として、大規模プロジェクトにおいてしばしば「コンパイラの限界」を突きつけてくる。シニアエンジニアであれば、ビルド時間が線形ではなく指数関数的に増大する瞬間に直面したことがあるだろう。

今回は、最も頻出する設計上の選択肢である「Interfaceの継承(`extends`)」と「交差型(`Intersection Types: &`)」の内部挙動を、型推論エンジン(`checker.ts`)の視点から解剖する。

—

1. コンパイラの視点:型評価の「キャッシュ」と「再計算」

まず、コンパイラの脳内を理解する必要がある。TypeScriptのコンパイラは、型を評価する際に「型キャッシュ」を利用する。

  • Interfaceの継承 (`interface A extends B`):

コンパイラは、`A` を評価する際、`B` のプロパティを単一の「シンボルテーブル」へと統合する。これはコンパイラにとって「フラットなオブジェクト」として記憶されるため、その後の参照において計算コストが極めて低い。

  • 交差型 (`type A = B & C`):

交差型は、コンパイラにとって「評価が必要な関数」に近い。`A` を参照するたびに、コンパイラは `B` と `C` のプロパティを突き合わせ、衝突を解決し、新しい型を構築しようと試みる。これは「再計算」のトリガーとなりやすい。

大規模なコードベースにおいて、交差型を多用すると、型チェックのたびに依存関係グラフの深層まで潜る必要が生じ、これがビルド時間を食いつぶす主因となる。

—

2. 厳密な違い:なぜ「交差型」は重いのか

以下のコードを比較してほしい。

// パターンA: Interface継承
interface Base { id: string; }
interface Derived extends Base { name: string; }

// パターンB: 交差型
type BaseT = { id: string; };
type DerivedT = BaseT & { name: string; };

一見同じに見えるが、TypeScriptの`checker.ts`が内部で保持するデータ構造は異なる。

1. プロパティの即時結合: `interface` は宣言した時点で構造が確定(あるいは拡張)されるため、コンパイラはシンボルをマージしてメモリ上に固定する。
2. 遅延評価: `type` は使用されるまで(あるいは推論が必要になるまで)実体化されない。特に複雑なジェネリクスが絡んだ交差型の場合、コンパイラは型が確定するまで再帰的に内部構造を走査し続け、スタックを消費する。

注意すべき「型のアラート」

特に、`Record & { prop: string }` のような、型定義が曖昧なもの同士を交差させると、コンパイラはプロパティの整合性を確認するために、全プロパティを列挙し直す。これが数千行に及ぶと、型チェックはイベントループを占有し、IDEのレスポンスが極端に低下する。

—

3. 実践的最適化:アーキテクトが守るべき原則

大規模アーキテクチャを設計する際、以下の戦略を遵守せよ。

① 基本は `interface`、特殊な演算は `type`

静的なドメインモデルやAPIのDTOは、常に `interface` で定義せよ。これにより、型が「名前付きエンティティ」としてキャッシュされ、コンパイラの負荷が劇的に下がる。

② 交差型の「フラット化」

複雑な型合成を行う場合は、`type` を積み重ねるのではなく、最終的な型を `interface` に固定する。

// 非推奨: 型の連鎖が深すぎて、コンパイラが型推論を諦める(anyに落ちる)リスクがある
type Complex = A & B & C & D;

// 推奨: 最終的な型をinterfaceで定義し、明確に構造を教える
interface Complex extends A, B, C, D {}

③ `keyof` や `Mapped Types` を併用する際の罠

`Intersection` を多用すると、`keyof T` の評価結果が巨大になり、ホバー時のIDE表示がフリーズする。「型を小さく保つ」ことは、単なる美学ではなく、コンパイラへの負担を減らすための工学的要請である。

—

結論:型システムの「重み」を制御せよ

TypeScriptは単なるJavaScriptのラッパーではない。型システムそのものが、コンパイル時に実行される「プログラム」である。

  • `interface` は、コンパイラにとっての静的な静的データベースだ。
  • `type` は、コンパイラにとっての計算式だ。

我々アーキテクトは、計算コストを最小化するために「静的な構造」を優先的に配置し、「計算式(交差型や条件型)」を必要な場所のみに限定すべきである。

ビルドが遅いと嘆く前に、自身の型定義が「コンパイラに計算させている」のか「コンパイラに構造を与えている」のかを再考せよ。コードの複雑性は、コンパイラのメモリ消費量と反比例する。この鉄則を理解した者だけが、真にスケーラブルなTypeScriptのコードベースを構築できるのだ。

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