TypeScript の Interface と Type Alias は、TypeScript のバージョンが上がるにつれ、どんどん同じような機能を有するようになってきています。
それならどっちかしか必要ないんじゃないか?
Interface で書くか Type Alias で書くかどちらでもよいのなら、迷ってしまいよくないのでは?
そんなことを考えてしまったので、ちゃんと調べてみました。
ちなみに、type は Type Alias または型エイリアスと呼ばれていることが多かったので、本記事でも Type Alias で通しています。
三行まとめ
- Interface はオープン
- Type Alias はクローズド
- どちらを使えばいいかはケースバイケース
Interface
Interface は、オブジェクトや関数、クラスの 仕様を定めるため のもの
用途
- クラスやオブジェクトの規格を定義する
- オブジェクト、クラス、関数の抽象型を定義するのに適している
性質
- 継承
- extends した際に同名のプロパティ(要素)を宣言した場合マージ(上書き)される
- type も継承できる
- オブジェクトと関数の型宣言のみしか使えない
- 宣言時に
=
が不要 - class への implements が可能
- MappedTypes が使えない
- index シグネチャ が使える
- 宣言されているプロパティ以外のプロパティが存在することが考慮される
-
拡張に対してオープン
- 同じ Interface を再宣言すると Interface 同士がマージされる
Type Alias
Type Alias は、複数の場所で再利用しようと思っている型に対して 名前をつけるためのもの
用途
- 型や型の組み合わせに別名を付ける
- 再利用したい複雑な型(複合的な型など)に名前を付けて再利用可能にする
- 複雑な型の宣言に適している
- 理由: 交差型, ユニオンタイプ、MappedType が使えるため
- ユーティリティタイプなども Type Alias を使って書く
性質
- 継承
-
交差型(&)を使えば同じようなことは可能
- ただし上書きはできない(後述)
- 交差型で Interface を
&
することも可能 -
&
した際に同名のプロパティ(要素)を宣言した場合重複する(後述)
-
交差型(&)を使えば同じようなことは可能
- オブジェクトと関数以外の型宣言でも使用可能
- プリミティブ、配列、タプルなど
- 宣言時に
=
が必要 - class への implements が可能
- MappedTypes が使える
- index シグネチャが使える
- 宣言されているプロパティ以外のプロパティが存在することが 考慮されない
- 拡張に対してクローズド
- 同じ type を宣言するとエラー
結局どっち使えばええのん?
ケースバイケースです。
強いて言うなら、コードベース(プロジェクト)全体を通した仕様としての型には Interface を、そうではない型には Type Alias を使うのがいいのかなと思いました。
参考コード
index シグネチャ
interface Options { [key: string]: any; }
function getUser (options?: Options = {}) {
// ...
}
const options = { params: { id: 21 } }
let user = getUser(options); // OK
MappedType
普通にやってたらあまり使わないけど、便利タイプを作る時とかわりと使うかも。
keyof
, typeof
などと合わせて使うことが多いかも。
type Fruits = 'Apple' | 'Orange' | 'WaterMelon';
// {
// 'Apple': number;
// 'Orange': number;
// 'WaterMelon': number;
// }
type PriceOfFruits = { [key in Fruits]: number; }
// NG
interface PriceOfFruits { [key in Fruits]: number; }
交差型(Intersection Type)
通称かつオペレータ。上書きしようとした場合、両方を満たす必要が出てくる。制約を追加するイメージ(まさに &
)。
type Point = { x: number, y: number }
type LabelledPoint = Point & { label: string } // GOOD
type Point2 = Point & { y: number | string } // NOT GOOD
const p2: Point2 = { x: 20, y: '20px' } // Error! y は number しか許容しない
拡張に対してオープン
interface User {
id: number;
name: string;
}
interface User {
age: number;
}
// User {
// id: number;
// name: string;
// age: number;
// }
宣言されているプロパティ以外のプロパティが存在することの考慮について
日本語がわかりづらいので具体的なコードで示す。
declare function getX(obj: { [key: string]: number }): number;
type Point = { x: number, y: number }
interface IPoint { x: number, y: number }
const pointA = { x: 1, y: 9, z: 0 }
getX(pointA) // OK
const pointB: Point = { x: 40, y: 120 }
getX(pointB) // OK
const pointC: IPoint = { x: 23, y: 46 }
getX(pointC) // Error!