TypeScript
TypeScriptでオブジェクトの型に別名を定義する方法には type文 と interface宣言 の2パターンがある。
- type文(型エイリアス / type alias)
type FooBarObj = {
foo: number;
bar: string;
}
const obj: FooBarObj = {
foo: 123,
bar: 'Hello, world!'
}
- interface宣言
interface FooBarObj = {
foo: number;
bar: string;
}
const obj: FooBarObj = {
foo: 123,
bar: 'Hello, world!'
}
type文 と interface宣言の違い
どちらも同じオブジェクトの型名の定義ができるが、何が違うのか。
1. 代入か宣言か
type文 → 型名を宣言する文。無名で作られた型に参照のため別名を与える。
interface宣言 → 名前が付いた型を宣言する。
2. 定義できる型の種類
type文 → オブジェクト以外の型にも使える
interface宣言 → オブジェクト型にのみ使える
3. オープンエンドと宣言マージ
TypeScript の interface には、オープンエンド(open-ended)と宣言マージ(declaration merging)という珍しい特徴がある。
同じ名前の interface を宣言してもエラーにはならず(オープンエンド)、同じ名前の interface を宣言した場合、それぞれのインターフェースの型がマージされる(宣言マージ)。
この機能を使った拡張した新しい型を生成することができる。type文はこのような拡張はできない。
※type文でも拡張自体はできる(参考)
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'
じゃあどっち使うの?
type文 も interface宣言 もどちらもオブジェクトの型を宣言できる、という点では同じであり、type文の方がより多くの場面で使用することができるので、interface宣言 は使わず type文 のみを使うという流儀もあるそう。
2014年以前には type文 は存在せず、interface宣言 のみ利用可能だった、という歴史的背景からオブジェクトには interface宣言 を使う人も多少いるのだとか。
宣言マージを使うタイミングが思いつかず、個人的にはむしろ拡張されたくないなと思ったので、とりあえず type文 を使う方が良さそう。(記事にも本にもとりあえず type文 って書かれてる)
ちなみに
interface宣言 でオープンエンドと宣言マージができる背景としても、JavaScript の歴史によるものが大きいような。
JavaScriptがアップデートされるにつれ、既存のクラスにもメソッドが追加されることがあります。たとえばArrayクラスはES2016でincludes()メソッドが、ES2019でflatMap()メソッドが追加されました。
TypeScriptの開発元は、JavaScriptのアップデートに合わせて、Arrayインターフェースの型定義も対応していく必要があります。単純に考えると、JavaScriptのバージョンごとに、Arrayインターフェースを独立して定義する方法が考えられます。
このアプローチは、一見すると良さそうです。しかし、よく考えてみると、JavaScriptがアップデートされるにつれ、インターフェースのコピペコードが増えていくという問題が出てきます。ES2015とES2016のArrayの違いは、includes()メソッドがあるかないかの違いだけです。それなのに、pop()メソッドやpush()メソッドといった多数のメソッドまでコピーしないといけなくなってしまいます。
これを解決するのが宣言マージです。
参考記事