TypeScript の interface と type:使い分けと実践パターン
TypeScript でオブジェクトの型を定義するとき、interface と type のどちらを使えばいいか迷うことがあります。どちらも似たことができますが、できないことと得意な場面が違います。
早わかりまとめ
| 機能 | interface | type |
|---|---|---|
| オブジェクト型定義 | ✅ | ✅ |
| 継承(extends) | ✅ | ✅(交差型で代替) |
| 宣言のマージ | ✅(同名で自動マージ) | ❌ |
| Union 型 | ❌ | ✅ |
| Intersection 型 | ❌ | ✅ |
| Mapped 型・Conditional 型 | ❌ | ✅ |
| クラスへの implements | ✅ | ✅ |
| プリミティブ型のエイリアス | ❌ | ✅ |
基本:どちらもオブジェクト型を定義できる
// interface
interface User {
id: number
name: string
email: string
}
// type
type User = {
id: number
name: string
email: string
}
シンプルなオブジェクト型はどちらでも書けます。
interface の特徴:宣言のマージ
同じ名前の interface を複数宣言すると自動でマージされます。
interface Window {
myCustomProperty: string
}
interface Window {
anotherProperty: number
}
// 上の2つが自動でマージされる
// Window は myCustomProperty と anotherProperty を持つ
ライブラリの型定義を拡張するときに使います。type では同じ名前の宣言はエラーになります。
interface の特徴:extends で継承する
interface Animal {
name: string
}
interface Dog extends Animal {
breed: string
}
const dog: Dog = {
name: 'Rex',
breed: 'Labrador',
}
複数の interface を extends できます。
interface Flyable {
fly(): void
}
interface Swimmable {
swim(): void
}
interface Duck extends Flyable, Swimmable {
quack(): void
}
type の特徴:Union 型・Intersection 型
type は Union 型(いずれか)や Intersection 型(すべてを持つ)を定義できます。
// Union 型(文字列か null)
type Status = 'active' | 'inactive' | 'pending'
type Nullable<T> = T | null
// Intersection 型(すべてを持つ)
type AdminUser = User & { role: 'admin' }
interface では Union 型は定義できません。
type の特徴:Mapped 型・Conditional 型
型を加工する高度な表現は type でのみ書けます。
// すべてのフィールドを optional にする
type Partial<T> = {
[K in keyof T]?: T[K]
}
// 特定の型かどうかで分岐する
type IsString<T> = T extends string ? 'yes' : 'no'
type Result = IsString<string> // 'yes'
クラスへの implements
どちらも implements で使えます。
interface Serializable {
serialize(): string
}
class User implements Serializable {
constructor(public name: string) {}
serialize() {
return JSON.stringify({ name: this.name })
}
}
実践的な使い分け
interface を使う場面
// ✅ オブジェクトの形を定義する(公開APIのシグネチャ)
interface ApiResponse {
data: unknown
status: number
message: string
}
// ✅ クラスの型を定義する
interface Repository<T> {
findById(id: number): Promise<T>
save(entity: T): Promise<T>
}
// ✅ ライブラリの型を拡張する(宣言マージ)
interface Window {
gtag?: (...args: unknown[]) => void
}
type を使う場面
// ✅ Union 型 type ButtonVariant = 'primary' | 'secondary' | 'danger' type ID = string | number // ✅ 型ユーティリティ type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>> // ✅ 関数型のエイリアス type EventHandler = (event: MouseEvent) => void // ✅ プリミティブ型のエイリアス type UserID = string type Timestamp = number
どちらを使うか迷ったら
チームでルールを統一するのが最優先ですが、ルールがない場合の目安です。
| ケース | 推奨 |
|---|---|
| オブジェクトの形を定義する | interface |
| クラスが実装する型を定義する | interface |
| ライブラリの型を拡張する | interface |
| Union 型・Intersection 型を使う | type |
| 型を計算・変換する | type |
| 迷ったとき | interface(拡張性が高い) |
まとめ
- interface:オブジェクト型・クラスの実装・型の拡張(宣言マージ)に向いている
- type:Union 型・Intersection 型・Mapped 型など複雑な型表現に向いている
- シンプルなオブジェクト型はどちらでも書けるが、チームでどちらかに統一するのがベスト
TypeScript のバージョン確認は「TypeScript バージョン確認:tsc version コマンドまとめ」を参照してください。