interfaceとtypeの違い、そして何を使うべきかについて
始め
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'
type
はinterface
と似てます。同じ例をinterface
からtype
に変えてみたら、書き方が少し変わっただけでほとんど同じように見えます。
型を定義する方法には他にも色々ありますが、今回はinterface
とtype
についてがテーマですので、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でtype
はinterface
のような拡張はできないと言ってましたが、そのような拡張のしかたができないだけで実は拡張自体はできます。
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
を使う理由は拡張性で、使いたくない理由も拡張性なところが興味深かったです。気になってた部分をたくさん調べてスッキリしました╭( ・ㅂ・)و!
Discussion
めっちゃわかりやすかったです!!!ありがとうございました!!