この記事ではTypeScriptの型定義でよく目にする「type」と「interface」について違いを整理します。
TypeScriptを使っていると、型を定義する際にtypeとinterfaceのどちらを使えばいいのか迷うことはありませんか?
自分もなんとなくしか違いを理解していなかったので改めて違いを整理してどうやって使い分けるか自分なりに紹介していこうと思います。
これに関する記事もよくありますが、実際にコードを書いて違いを実感しないと深い理解につながらないのでコードでどのような違いが出るのか調べてみます。
プロジェクトによってどちらかだけを使うようにしたりするところもあるようですが、違いを知っておくと役に立つこともあると思いますので参考にしてみてください。
この記事を読むメリット
- typeとinterfacの違いを理解できる
- 使い分ける判断基準がなんとなく分かる
typeとinterfaceの基本的な使い方
まずは基本的な使い方から。
typeは以下のように定義します。
type User = {
name: string
age: number
};
interfaceはこちら。
interface User {
name: string
age: number
}
どちらも同じ型定義ですが、書き方に若干の違いがあります。
typeは型の「代入」となるので最後にセミコロンが推奨となっています。
interfaceは型の「宣言」であるためセミコロンは不要になります。
「fnction test() {}」と「const test = function() {};」の違いとイメージすればわかりやすいと思います。
実際に型として利用する場合は同じで、以下のようにします。
type User = {
name: string;
age: number;
};
const user1: User = {
name: '山田',
age: 30,
};
return (
<div className="p-4">
<h1 className="text-2xl font-bold">Type Test</h1>
<p className="mt-2">名前: {user1.name}</p>
<p>年齢: {user1.age}</p>
</div>
);
nameとageを定義した場合に正常にユーザーが定義できるようになっています。

typeとinterfaceの違い
続いては違いを紹介します。
いくつかの観点から実際にどのような違いがあるかコードを使って理解していきます。
拡張性
まずは拡張性です。
interfaceとtypeではどちらも型を拡張させることができます。
iterfaceだと「extends」を使った継承です。
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
const myDog1: Dog = {
name: 'Pochi',
breed: 'Shiba',
};
// プロパティ 'breed' は型 '{ name: string; }' にありませんが、型 'Dog' では必須です。
const myDog2: Dog = {
name: 'Pochi',
};
Dogの型をAnimal型を拡張して定義しています。
ですのでmyDog1のように「name」「breed」を定義する必要があります。
「name」しか定義していないmyDogs2はエラーになります。
typeでは合成というものを使って拡張をすることができます。
type Animal = {
name: string;
};
type Dog = Animal & {
breed: string;
};
const myDog1: Dog = {
name: 'Pochi',
breed: 'Shiba',
};
// プロパティ 'breed' は型 '{ name: string; }' にありませんが、型 'Dog' では必須です。
const myDog2: Dog = {
name: 'Pochi',
};
typeも同じように拡張ができ、myDog2はエラーになります。
宣言のマージ
さらにinterfaceでは、「宣言のマージ」というものができます。
以下が例です。
interface Dog {
name: string;
}
interface Dog {
breed: string;
}
const myDog1: Dog = {
name: 'Pochi',
breed: 'Shiba',
};
同じ宣言をすると、型がマージされて拡張したような動きになります。
typeでは同じtypeを再定義することはできません。
iterfaceを好まない意見が多い理由の1つがこの宣言のマージができることです。
マージができることで予期せぬところで型が変更されていってしまう可能性があります。
定義可能な型の種類
最後は定義できる型の種類についてです。
ここも大きな違いがあり、typeは万能でinterfaceはオブジェクト型のみという特徴があります。
type UserId = string; // プリミティブ型
type Status = 'success' | 'error'; // リテラル型 + ユニオン型
type Point = { x: number } & { y: number }; // 交差型(intersection)
type Nullable<T> = T | null; // ジェネリクス + ユニオン
type StringArray = string[];
type Coordinate = [number, number];
type GreetFn = (name: string) => string;
typeはこのようにプリミティブ型・ユニオン型や関数型といった型を定義できます。
しかし、interfaceでは上記のような方は定義できません。あくまでオブジェクト構造のみです。
interface Animal {
name: string;
speak(): void;
status: 'happy' | 'sad';
}
しかし、オブジェクトの中では、このように自由度高く定義が可能です。
エンジニアにおすすめ書籍
エンジニアになりたて、これから勉強を深めていきたいという方におすすめの書籍はこちら!
typeとinterfaceどちらを使うべきか
ここまでの違いをまとめてみます。
特徴 | type | interface |
---|---|---|
オブジェクト型定義 | ◯ | ◯ |
Union型(’A’ | ‘B’) | ◯ | × |
型エイリアス | ◯ | × |
宣言のマージ | × | ◯ |
拡張性 | ◯ | ◯ |
これらの違いを整理すると、個人的には「オブジェクト型ならinterface」「それ以外はtype」がシンプルで使いやすいかなと思いました。
そして宣言のマージは便利ですが、バグに繋がりやすいので使わない、型宣言の書く位置を固定するなどの工夫が必要だなと思いました。
改めて実際にコードを書きながら整理すると両者の違いを理解できました。
実務だとすでにルールなどが決まっていてどちらを使った方が良いかなどと考える機会が少ないかもしれませんが理解しておくといざという時に役立つかもしれません。
参考にしてみてください。