TypeScriptのinterfaceとtypeの違いは?使い分けを初心者向けに解説

TypeScriptのinterfaceとtypeの違いを、JavaScript経験者が理解するまで
少年A
interfaceとtypeって、どっちもオブジェクトの型を書けるけど、何が違うの?
キノ
実は、普通のオブジェクトならほぼ同じなんだ。違いが出る場面だけ押さえれば迷わなくなるよ
少年A
じゃあ、どんなときに違いが出るの?
キノ
大きくは2つ。ユニオン型と、同じ名前での定義の追加。順番に見ていこう

TypeScriptのinterfaceとtypeの違いとは?結論は2つだけ

TypeScript の interface と type の違いは、主にユニオン型などを書けるのが type、同じ名前で定義を追加できるのが interface という2点です。オブジェクトの形を表すだけなら、どちらを使ってもほとんど同じように動きます。

TypeScript interface と type の違い:ユニオン型と宣言のマージの2点を比較した図
interfacetype
オブジェクトの型書ける書ける
ユニオン型(A | B)書けない書ける
プリミティブ・タプル・関数型に別名書けない(関数型は呼び出しシグネチャで可)書ける
同じ名前で定義を追加(宣言のマージ)できるエラーになる
継承・合成extends&(交差型)
スポンサーリンク

基本の書き方:オブジェクトの型はどちらでも書ける

まず、ユーザー情報のような単純なオブジェクトを、両方の書き方で定義します。

interface UserI {
  name: string;
  age: number;
}
type UserT = {
  name: string;
  age: number;
};
const a: UserI = { name: "キノ", age: 30 };
const b: UserT = a; // 相互に代入できる

2つの型は互いに代入できます。つまり、単純なオブジェクトなら interface と type のどちらを使っても動作に差はありません。

オブジェクトの型の基本は、こちらで解説しています。

スポンサーリンク

違い1:typeはユニオン型が書ける

ユニオン型は「このうちどれか」を表す型です。type では、リテラルを | でつないで使えます。

type Status = "success" | "error" | "loading";

function showMessage(status: Status) {
  console.log(status);
}
showMessage("success");
showMessage("done");
// Argument of type '"done"' is not assignable to parameter of type 'Status'.

Status に含まれない "done" を渡すと、コンパイル時にエラーになります。この「選択肢を限定する」書き方は interface ではできないため、ユニオン型が必要なら type を使います。

プリミティブ型やタプル、関数型に別名を付けるときも type を使います。

type Id = string | number;
type Point = [number, number];
type Handler = (event: string) => void;
interface Props {
  onClick: (event: string) => void;
}
const p: Point = [1, 2];

違い2:interfaceは同じ名前で定義を追加できる

interface は、同じ名前で複数回書くと、定義が自動的にひとつにまとまります。これを宣言のマージ(declaration merging)と呼びます。

interface User {
  name: string;
}
interface User {
  age: number;
}
const u: User = { name: "キノ", age: 30 }; // 2つがマージされる

type で同じことをすると、エラーになります。

type User = {
  name: string;
};
type User = {
  age: number;
};
// Duplicate identifier 'User'.

この性質は、すでにある型へプロパティを足したいときに役立ちます。たとえばブラウザの Window にプロパティを追加する場合です。

declare global {
  interface Window {
    appName: string;
  }
}
window.appName = "kinoblog";

一方で、意図せず同名の interface を書くと、エラーにならずにマージされる点には注意が必要です。

継承の書き方の違い:extendsと交差型

既存の型を拡張するとき、interface は extends、type は交差型(&)を使います。

interface Animal {
  name: string;
}
interface Dog extends Animal {
  breed: string;
}
type Cat = Animal & { color: string };
const d: Dog = { name: "ポチ", breed: "柴" };
const c: Cat = { name: "タマ", color: "白" };

どちらも「Animalの内容を引き継いで、プロパティを足す」という結果は同じです。TypeScript公式ドキュメントには、extends を使った interface のほうが、交差型の type よりコンパイラの処理が速いことが多いと書かれています。

ジェネリクスでも使える:どちらも型引数を受け取れる

interface も type も、ジェネリクスで型引数を受け取れます。この点は使い分けの理由になりません。

type Response<T> = { data: T; status: number };
interface Box<T> {
  value: T;
}
const r: Response<string> = { data: "ok", status: 200 };
const bx: Box<number> = { value: 1 };

実務での使い分け:迷ったらinterface、ユニオンならtype

TypeScript公式ドキュメントでは、多くの場合どちらを選んでも構わず、迷ったら interface を使い、type の機能が必要になったら type を使う、という目安が示されています。

場面おすすめ
オブジェクトの形を表すどちらでもよい(チームの規約に合わせる)
ユニオン型・リテラル型の組み合わせtype
タプル・関数型・プリミティブの別名type
既存の型にプロパティを追加(ライブラリの型の拡張など)interface
Reactコンポーネントのpropsどちらでもよい(プロジェクト内で統一)

もっとも大切なのは、プロジェクト内で書き方を統一することです。「オブジェクトは interface、それ以外は type」のように、ルールを決めておくとレビューで迷いません。

公式ドキュメントの比較は、こちらにまとまっています。

よくあるエラーと対処

1. Duplicate identifier ‘User’

type で同じ名前を2回定義したときのエラーです。名前を変えるか、追加したいなら interface に変更します。

2. Argument of type ‘”done”‘ is not assignable to parameter of type ‘Status’

ユニオン型に含まれない値を渡したときのエラーです。選択肢に追加するか、渡す値を直します。

3. 型アサーションで型を合わせたくなったとき

型が合わないからと as で押し切ると、実行時のエラーを見逃しやすくなります。先に型定義側を見直してください。

よくある質問

Q. interfaceとtype、初心者はどちらを使えばいいですか?
A. まずは type でも interface でも構いません。ユニオン型が必要な場面で type、迷ったら interface という目安で十分です。大切なのはプロジェクト内で統一することです。

Q. 実務ではどちらが多いですか?
A. プロジェクトの規約によって異なります。Reactのpropsでもどちらも使われており、片方に決まっているわけではありません。

Q. interfaceでユニオン型は書けませんか?
A. interface そのものでは書けません。ただし、type で作ったユニオン型を、interface のプロパティの型として使うことはできます。

Q. パフォーマンスに差はありますか?
A. 公式ドキュメントでは、extends を使った interface は交差型の type よりコンパイラに有利なことが多いとされています。ただし、小規模なアプリで気にする必要はほとんどありません。

まとめ:TypeScript interfaceとtypeの違いのおさらい

  • オブジェクトの型だけなら、interface と type はほぼ同じ
  • ユニオン型・タプル・関数型の別名は type で書く
  • 同じ名前で定義を追加(宣言のマージ)できるのは interface だけ
  • 継承は interface が extends、type が交差型(&)
  • 迷ったら interface、type の機能が必要なら type。プロジェクト内で統一する

型注釈の基本から整理したい方は、こちらもあわせてどうぞ。

少年A
ユニオン型が要るかどうかで決めればいいんだね。思ったよりシンプルだった
キノ
そう。あとはプロジェクトで統一しておけば、迷う場面はほぼなくなるよ

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です