なぜ interface と type の両方があるのか

TypeScript には interfacetype という、一見似たような機能が 2 つある。どちらもオブジェクトの型を定義できるため、初学者は混乱しがちだ。なぜ両方存在するのか、歴史的経緯と設計思想から整理する。

歴史的な経緯

TypeScript 0.8(2012 年)の時点では interface しか存在しなかった。これは Java や C# のインターフェースに近い概念で、オブジェクトの「形」を定義するために導入された。

type(型エイリアス)が追加されたのは TypeScript 1.4(2015 年)からだ。当初はユニオン型やタプル型に別名を付けるための機能だったが、バージョンを重ねるごとに機能が拡張され、現在ではオブジェクト型の定義にも使えるようになっている。

interface(2012 年〜)

オブジェクト指向言語の伝統を受け継いだ機能。クラスとの連携や宣言マージを前提に設計されている。

type(2015 年〜)

型に別名を付けるための機能として出発。ユニオン型や交差型など、より柔軟な型表現が可能。

できること・できないこと

両者の機能は年々近づいているが、今でも明確な違いがある。

// interface: 宣言マージができる
interface User {
  name: string;
}
interface User {
  age: number;
}
// User は { name: string; age: number } になる

// type: 同名の再宣言はエラー
type Product = {
  name: string;
};
// type Product = { price: number };  // エラー

宣言マージは、ライブラリの型定義を拡張する場面で役立つ。例えば Express の Request オブジェクトにカスタムプロパティを追加したいときなどだ。

一方、type にしかできないこともある。

// ユニオン型のエイリアス
type Status = "pending" | "approved" | "rejected";

// プリミティブ型のエイリアス
type ID = string | number;

// 条件型
type NonNullable<T> = T extends null | undefined ? never : T;

interface はあくまでオブジェクトの形を定義するもので、ユニオン型やプリミティブ型に別名を付けることはできない。

どちらを使うべきか

TypeScript 公式ドキュメントでは、基本的に interface を推奨している。理由はエラーメッセージが読みやすいこと、コンパイルが若干速いことなどが挙げられている。

ただし実際のプロジェクトでは、チームやコミュニティの慣習に従うほうが重要だ。React 界隈では type が主流で、Angular 界隈では interface が多い傾向にある。

interface を選ぶ場面

ライブラリの型定義を書くとき、クラスに実装させるとき、拡張される可能性がある API の型を定義するとき。

type を選ぶ場面

ユニオン型やタプル型を定義するとき、既存の型を組み合わせて新しい型を作るとき、関数コンポーネントの Props を定義するとき(React の慣習)。

結論として、両方あるのは「それぞれ得意分野が違うから」だ。歴史的に別々の目的で生まれた機能が、互いの領域に踏み込みながらも、今なお棲み分けを保っている。どちらか一方に統一されない理由は、後方互換性と、それぞれのユースケースに最適化された設計が残っているためである。