まずざっくり:「どっちも“型を定義する道具”」
type も interface も、「型に名前をつける」ための仕組みです。
どちらもオブジェクトの形を表現できます。
interface User {
id: number;
name: string;
}
type User2 = {
id: number;
name: string;
};
TypeScriptこの2つは、ほぼ同じ意味です。
だからこそ、初心者が最初に混乱するのは自然です——「違いがあるようで、ないように見える」から。
違いは主に「拡張の仕方」「宣言のマージ」「表現できる型の幅」にあります。
ここを、実際のコードと一緒にかみ砕いていきます。
共通点:どちらも「オブジェクトの形」を表現できる
オブジェクト型としての使い方はほぼ同じ
interface Person {
name: string;
age: number;
}
type PersonType = {
name: string;
age: number;
};
const p1: Person = { name: "Taro", age: 20 };
const p2: PersonType = { name: "Hanako", age: 18 };
TypeScriptどちらも「name: string と age: number を持つオブジェクト」という意味です。
このレベルでは、どちらを使ってもほぼ差はありません。
だから実務でも、「プロジェクトとしてどちらかに寄せる」「チームのルールに従う」といった運用が多いです。
大きな違い1:宣言マージ(同じ名前をあとから足せるか)
interface は「あとから足せる」
interface User {
name: string;
}
interface User {
age: number;
}
const u: User = {
name: "Taro",
age: 20,
};
TypeScript同じ名前の interface User を2回書くと、自動的にマージされて1つの型になります。
結果として User は { name: string; age: number } という形になります。
これは「宣言マージ」と呼ばれる機能で、interface の大きな特徴です。
type は「同じ名前で2回は書けない」
type User = {
name: string;
};
type User = {
age: number;
}; // エラー:同じ名前で再定義はできない
TypeScripttype は同じ名前で複数回宣言できません。
「1つの名前に1つの定義だけ」というルールです。
まとめると:
- 「あとから同じ名前にプロパティを足したい」→
interfaceならできる - 「1つの名前に1つの定義だけでいい」→
typeでも問題ない
宣言マージは便利ですが、やりすぎると「どこで何が足されているか分かりにくくなる」ので、
大規模なコードベースでは慎重に使われます。
大きな違い2:拡張の書き方(extends と &)
interface の拡張(extends)
interface User {
id: number;
name: string;
}
interface AdminUser extends User {
permissions: string[];
}
const admin: AdminUser = {
id: 1,
name: "Taro",
permissions: ["user:read", "user:write"],
};
TypeScriptextends を使って、「既存の interface をベースに新しい interface を作る」ことができます。
オブジェクト指向の「継承」に近い感覚です。
type の拡張(交差型 &)
type User = {
id: number;
name: string;
};
type AdminUser = User & {
permissions: string[];
};
TypeScripttype では &(交差型)を使って、「型同士を合成」します。
結果として意味はほぼ同じですが、書き方のスタイルが少し違います。
感覚としては:
- 「クラスっぽく継承したい」「オブジェクトの“形”を素直に伸ばしたい」→
interfaceのextends - 「いろんな型を合成して1つにしたい」「ユニオンや交差を駆使したい」→
typeの&や|
という使い分けがしっくりきます。
大きな違い3:表現できる型の幅(type の方が守備範囲が広い)
type は「何にでも名前をつけられる」
type UserId = number;
type Status = "success" | "error" | "loading";
type Point = [number, number];
type Handler = (value: string) => void;
TypeScripttype は、プリミティブ型・ユニオン型・タプル・関数型など、ほぼ何にでも名前をつけられます。
一方、interface は基本的に「オブジェクトの形」を表現するためのものです。
(関数型も書けますが、ユニオン型などは扱えません。)
interface Handler {
(value: string): void;
}
TypeScriptなので、
- 「オブジェクトの形」→
interfaceでもtypeでも書ける - 「ユニオン型・タプル・プリミティブに名前をつけたい」→
type一択
という整理になります。
実務でのざっくりした使い分けの指針
よく言われる方針(あくまで“目安”)
いろんな記事やスタイルガイドを総合すると、よく出てくる方針はこんな感じです。
- 「プリミティブ・ユニオン・タプルなど“形以外”の型」→
type - 「オブジェクトの形(特にクラスと一緒に使うもの)」→
interface - 「迷ったらどちらでもいいが、プロジェクトのルールに合わせる」
Google のスタイルガイドなどでは、
「オブジェクトには interface、それ以外には type を使う」といった方針も紹介されています。
ただし、TypeScript の公式として「絶対の正解」はなく、
「チームで統一されていること」「自分たちが読みやすいこと」が一番大事だとよく言われます。
初心者向けの結論:「まずはこう決めてしまっていい」
いきなり全部の違いを完璧に覚える必要はありません。
最初のうちは、次のように割り切ってしまって大丈夫です。
- 「オブジェクトの形に名前をつける」→ どちらかに統一(たとえば
interface) - 「ユニオン型・タプル・プリミティブに名前をつける」→
type - 「プロジェクトや教材がどちらかを推しているなら、それに合わせる」
大事なのは、「型に名前をつけて、意味をはっきりさせる」習慣そのものです。type か interface かで悩みすぎるよりも、
「このデータにはどんな形と意味があるのか」を型として表現していくことに、意識を多めに割いてほしい。
そのうえで、「宣言マージが必要だから interface にしよう」「ユニオンを使うから type にしよう」といった判断は、
少しずつ経験と一緒に積み上げていけば十分間に合います。
