なぜ interface と type の両方があるのか
TypeScript には interface と type という、一見似たような機能が 2 つある。どちらもオブジェクトの型を定義できるため、初学者は混乱しがちだ。なぜ両方存在するのか、歴史的経緯と設計思想から整理する。
歴史的な経緯
TypeScript 0.8(2012 年)の時点では interface しか存在しなかった。これは Java や C# のインターフェースに近い概念で、オブジェクトの「形」を定義するために導入された。
type(型エイリアス)が追加されたのは TypeScript 1.4(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 が多い傾向にある。
ライブラリの型定義を書くとき、クラスに実装させるとき、拡張される可能性がある API の型を定義するとき。
ユニオン型やタプル型を定義するとき、既存の型を組み合わせて新しい型を作るとき、関数コンポーネントの Props を定義するとき(React の慣習)。
結論として、両方あるのは「それぞれ得意分野が違うから」だ。歴史的に別々の目的で生まれた機能が、互いの領域に踏み込みながらも、今なお棲み分けを保っている。どちらか一方に統一されない理由は、後方互換性と、それぞれのユースケースに最適化された設計が残っているためである。