TypeScript の interface と type:使い分けと実践パターン

スポンサーリンク

TypeScript の interface と type:使い分けと実践パターン

TypeScript でオブジェクトの型を定義するとき、interfacetype のどちらを使えばいいか迷うことがあります。どちらも似たことができますが、できないことと得意な場面が違います。


早わかりまとめ

機能 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 コマンドまとめ」を参照してください。