interfaceとtypeの違い、そして何を使うべきかについて

5 min read読了の目安(約5100字 1

始め

TSを初めて勉強したときに「typeよりinterfaceを使うように!」というブログをよく見ましたが、業務の時は全部typeで型を定義してました。あれ?と疑念に思ってましたので、記事でお話させていただきます。

1. 型の定義

TSで一番手っ取り早く型を定義できる方法は多分これだと思います。

let level: number = 15;

level = 15

level = "十五" //Type 'string' is not assignable to type 'number'

このように宣言時の変数に方の注釈をつけることを型アノテーション(Type Annotation)と言います。上の例だけ見たら特に問題なさそうに見えますが、コードが少しでも複雑になったら問題があります。

let apple: { nickName: string; isHuman: boolean; level: number } = {
  nickName: "りんご",
  isHuman: true,
  level: 0
};

このようにオブジェクトの型を定義するときに型アノテーションでやったら大変だし、読みづらいし、面倒くさいです。

1-1. interface

というわけで、TSではオブジェクトの型に名前をつけることができます。それがinterfaceです。

interface Member {
  nickName: string;
  isHuman: boolean;
  level: number;
}

let apple: Member = {
  nickName: "りんご",
  isHuman: true,
  level: 0
};

apple.isHuman = "yes" //Type 'string' is not assignable to type 'boolean'

変数の隣に型名を書いといたら済む話なので、先の型アノテーションよりよほど楽ですね。

1-2. type

型に名前をつけられる方法としてはtypeというやつもあります。正式には型エイリアス(Type Alias)という名称で、書き方としてtypeを書いています。

type Member = {
  nickName: string;
  isHuman: boolean;
 level: number;
};

let apple: Member = {
  nickName: "りんご",
  isHuman: true,
  level: 0
};

apple.isHuman = "yes" //Type 'string' is not assignable to type 'boolean'

typeinterfaceと似てます。同じ例をinterfaceからtypeに変えてみたら、書き方が少し変わっただけでほとんど同じように見えます。

型を定義する方法には他にも色々ありますが、今回はinterfacetypeについてがテーマですので、2つがどう違うかについてお話します。

2. interfaceとtypeの違い

2-1. 宣言と代入

interfaceは型の宣言ですので、型に名前をつけることができますtypeはどちらかというと無名で作られた型に参照のため別名をを与えるということをやってます。

interface book {
  title: string;
  pages: number;
}

type book = {
  title: string;
  pages: number;
};

よぉく見たらinterfaceにはセミコロンがなくてtypeにはセミコロンが付いていることに気づきます。interface構文はブロック{}で終わる文なのでセミコロンが不要で、typeは最終的に代入文になるのでセミコロンが必要になります。

function dummy () { ... }
const dummy = function () { ... };

上記のように関数宣言と関数式の考え方を思い出したら少し理解しやすいかと思います。

すごく細かいですが、このような違いがあります。

2-2. 定義できる型の種類

interfaceではオブジェクトとクラスの型だけ定義できますが、typeでは他の型も参照できます

type Color = "白" | "黒" | "赤" | "緑";

let color: Color = "白";
color = "青"; //Type '"青"' is not assignable to type 'Color'.

オブジェクト以外の型も参照できるため、typeではこういうこともできます。

2-3. 拡張

interface拡張ができます

interface User {
  name: string;
}

interface User {
  level: number;
}

const user: User = {
  name: "apple",
  level: 0
};

const user2: User = {
  name: "banana"
}; //Property 'level' is missing in type '{ name: string; }' but required in type 'User'

サンプルコードを見たらわかりやすいです。再宣言しているように見えますが、実際は新しいプロパティの型定義を追加しています。そのため、user2ではlevelプロパティがないと怒られています。

typeではこのような拡張はできません

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

type User = {
  level: number;
} //Duplicate identifier 'User'

3. どちらを使う?

3-1 interface派

始めの部分で申し上げた通りTSに関する記事を探したらinterfaceを使うようにと言ってるところも多いです。そしてその根拠としては主に拡張性をあげています。公式ドキュメントでもinterfaceのほうが拡張にオープンなJavascriptのオブジェクト動作方式に似ているという理由でinterfaceをおすすめしています。

Because an interface more closely maps how JavaScript objects work by being open to extension, we recommend using an interface over a type alias when possible.

3-2 type派

ここでいきなりですが、2-3の新しい例をあげてみます。

interface Shoes {
  size: number;
  color: string;
}


// コード1万行


const heel: Shoes = {
  size: 235,
  color: "red"
}; //Property 'isSecondhand' is missing in type '{ size: number; color: string; }' but required in type 'Shoes'

ちゃんとShoesの型に合わせてオブジェクトを宣言しているのに、なぜかわけのわからないエラーが起きています。おかしいと思って探したら、あのコード1万行の中にこういう処理が入ってました。

interface Shoes {
  isSecondhand: boolean;
}

このように、拡張できるというのはある意味私が知らないうちに拡張されてる可能性があることを示します。これは嬉しくないです。

また、2-3でtypeinterfaceのような拡張はできないと言ってましたが、そのような拡張のしかたができないだけで実は拡張自体はできます

type ErrorHandling = {
  success: boolean;
  error?: { message: string };
};

type ArtistsData = {
  artists: { name: string }[];
};

type ArtistsResponse = ArtistsData & ErrorHandling;

const dummyData: ArtistsResponse = {
  artists: [{ name: "apple" }, { name: "banana" }],
  success: true
};

公式ドキュメントの例がわかりやすかったので、一部持ってきました。ご覧の通り&を使ったら既存のタイプを組み合わせることができます。

ちなみに全く同じ例をinterfaceで書き換えたらこうなります。

interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}

interface ArtistsData {
  artists: { name: string }[];
}

interface ArtistsResponse extends ErrorHandling, ArtistsData {}

const dummyData: ArtistsResponse = {
  artists: [{ name: "apple" }, { name: "banana" }],
  success: true
};

個人的はtypeで表現したほうが直感的でわかりやすい気がします。

interfaceでできることが大体typeでもできるし、typeのほうが表現できる範囲が広いし、知らないうちに拡張されたくないから最初からtypeでよいということがtype派の理由です。

TS初期はinterfaceではできてtypeはできないことが割とあったのでinterface派が多かったです。しかし、バージョンアップしてるうちにtypeでもできることがどんどん増えました。挙げ句、interfaceを使う理由がなくなり、type派が多くなったということが現状でしょう。

終わり

interfaceを使う理由は拡張性で、使いたくない理由も拡張性なところが興味深かったです。気になってた部分をたくさん調べてスッキリしました╭( ・ㅂ・)و!

この記事に贈られたバッジ