【TypeScript】typeとinterface、結局どちらを使うべき?

JavaScript

こんにちは、かつコーチです。

前回は、リテラル型で値を限定する方法を解説しました。

TypeScriptで型を定義しようとすると、必ずぶつかるのが「typeinterface、どっちを使えばいいの?」という疑問です。

どちらもオブジェクトの形を定義できて、見た目もよく似ています。

今日はこの2つの違いと、実務での使い分け方を整理していきます。

typeとinterfaceの基本の書き方

type文の基本形

type は「型に名前をつける」ためのキーワードです。

type User = {
  id: number;
  name: string;
  email: string;
};

const user: User = {
  id: 1,
  name: "かつコーチ",
  email: "katsu@example.com",
};

オブジェクトの形だけでなく、次のようにユニオン型やプリミティブ型にも名前をつけられます。

type Status = "active" | "inactive" | "pending";
type Id = number | string;

interface文の基本形

interface も同じくオブジェクトの形を定義しますが、書き方が少し違います。

interface User {
  id: number;
  name: string;
  email: string;
}

const user: User = {
  id: 1,
  name: "かつコーチ",
  email: "katsu@example.com",
};

見た目はほぼ同じで、= がないことと、末尾にセミコロンではなく改行だけで終わることが多い点くらいしか違いに気づきません。

私も最初にTypeScriptを学んだとき、「この2つ、結局何が違うんだ」と数分固まった記憶があります。

正直、単純なオブジェクトの型定義だけを見ている限りは、見た目上の違いしか分かりません。

違いが表に出てくるのは、もう少し複雑なことをやろうとしたときです。

typeとinterfaceの決定的な違い

拡張のしかたが違う

まず大きな違いは「型を拡張する方法」です。

interface は同じ名前で複数回宣言すると、自動的にマージされます(これを「宣言のマージ」と呼びます)。

interface Animal {
  name: string;
}

interface Animal {
  age: number;
}

// AnimalはnameとageのプロパティをもつP方の型として扱われる
const dog: Animal = {
  name: "ポチ",
  age: 3,
};

一方 type は同じ名前で2回定義すると、エラーになります。

type Animal = {
  name: string;
};

// ❌ エラー:Duplicate identifier 'Animal'
type Animal = {
  age: number;
};

type で型を拡張したい場合は、交差型(&)を使います。

type Animal = {
  name: string;
};

type Pet = Animal & {
  age: number;
};

const dog: Pet = {
  name: "ポチ",
  age: 3,
};

interfaceのextendsとtypeの交差型

interfaceextends を使って、他の interface を継承できます。

interface Animal {
  name: string;
}

interface Pet extends Animal {
  age: number;
}

const dog: Pet = {
  name: "ポチ",
  age: 3,
};

type の場合は &(交差型)を使うことで、似たようなことが実現できます。

書き方は違いますが、できあがる型はほぼ同じになるケースが多いです。

ユニオン型・プリミティブ型を扱えるか

もう一つの大きな違いは、type はオブジェクト以外の型にも名前をつけられる点です。

// interfaceでは書けない
type Direction = "up" | "down" | "left" | "right";
type Callback = (value: number) => void;
type Point = [number, number];

interface はオブジェクトの形(と関数の形の一部)しか表現できないため、ユニオン型やタプル型に名前をつけたいときは、必然的に type を使うことになります。

私が実務で最初に混乱したのはここで、「ユニオン型に interface Status = "active" | "inactive" と書こうとしてエラーになる」という失敗をやりました。

interface のあとに = を書くこと自体が文法として存在しないため、素直に type へ書き換える必要があります。

typeとinterfaceの比較表

どちらを選ぶべきかの判断軸

ここまでの違いを整理すると、次の比較表のようになります。

観点typeinterface
オブジェクトの型定義できるできる
ユニオン型・タプル型できるできない
同名での再宣言(マージ)エラーになる自動でマージされる
拡張の書き方交差型(&extends
クラスへの実装implements で可能implements で可能
エラーメッセージの分かりやすさやや複雑になりがち比較的シンプル

実務での使い分けの目安

ここからは私の実務経験も踏まえた、あくまで一つの目安です。

  • オブジェクトの形(API のレスポンス、コンポーネントの props など)を定義するなら interface
  • ユニオン型・タプル型・関数型など、オブジェクト以外の型に名前をつけるなら type

多くのチームで採用されている考え方として「オブジェクトの型は基本 interface、それ以外は type」というルールがあります。

理由は、interface の方が拡張の意図が extends というキーワードで明示され、エラーメッセージも比較的読みやすいためです。

ただし、これは絶対のルールではなく、プロジェクトやチームのコーディング規約によって「すべて type に統一する」という方針を取っているところも珍しくありません。

大事なのは「どちらが正解か」ではなく「チーム内でルールを統一する」ことです。

実務でよくあるつまずきポイント

propsの型定義で混在させてしまう

Reactのコンポーネントを書いていると、props の型に typeinterface が混在してしまうことがあります。

❌ Before:同じプロジェクト内でtypeとinterfaceが混在

// UserCard.tsx
interface UserCardProps {
  name: string;
  age: number;
}

// ProductCard.tsx
type ProductCardProps = {
  title: string;
  price: number;
};

どちらも動作はしますが、ファイルによって書き方がバラバラだと、レビュー時に「これはどっちのルールだっけ?」と余計な確認コストが発生します。

✅ After:チームでルールを統一する

// UserCard.tsx
interface UserCardProps {
  name: string;
  age: number;
}

// ProductCard.tsx
interface ProductCardProps {
  title: string;
  price: number;
}

私が以前担当したプロジェクトでも、途中から複数人でコードを書くようになったタイミングで typeinterface が混在し始め、ESLintのルール(consistent-type-definitions)で強制的に統一した経験があります。

こういったルールは、リンターで機械的に縛ってしまうのが一番確実です。

interfaceの拡張を交差型と勘違いする

もう一つよくある失敗が、interface の拡張時に & を使おうとしてしまうケースです。

interface Animal {
  name: string;
}

// ❌ interfaceの拡張に&は使えない(構文エラーになる)
interface Pet & Animal {
  age: number;
}

interface の拡張は必ず extends を使います。

interface Animal {
  name: string;
}

interface Pet extends Animal {
  age: number;
}

typeinterface を行き来していると、こういった構文の取り違えは意外とよく起こります。

まとめ

この記事のポイント

  • type はオブジェクトだけでなく、ユニオン型やタプル型にも名前をつけられる
  • interface は同名宣言が自動マージされ、extends で拡張する
  • オブジェクトの型は interface、それ以外は type という使い分けが一つの目安
  • どちらが正解というより、チームでルールを統一することが重要
  • ESLintなどのリンターでルールを強制すると、混在を防ぎやすい

次に読むべき記事

次回は、TypeScriptの列挙型「enum」の使い方について解説していきます。

→ 次の記事:enum(列挙型)の使い方

タイトルとURLをコピーしました