TypeScriptにはオブジェクトの型を表現する手段としてtypeを使う方法とinterfaceを使う方法があります。 どちらも機能としてとても似たようなところがあるのですがいくつかの違いもあります。 早速一つずつ見ていきましょう。
type
オブジェクトの型はtypeを使うことで以下のように表現することができます。
type Human = {
name: string;
age: number;
birthday: Date;
}
let man: Human;
オプショナル
オブジェクトのプロパティに値があってもなくてもどちらでもいいよという場合は以下のように?をつけます
type Human = {
name: string;
age: number;
birthday?: Date;
}
const human: Human = {
name: "daigo",
age: 30,
// birthdayがなくてもOK
}
&演算子を使ったType同士の合体
また以下のように型と型同士を&
を使って組み合わせて使うこともできます。
type TextMessage = {
text: string;
}
type ImageMessage = {
imageUrl: string;
alt: string;
}
type Message = TextMessage & ImageMessage;
この時型のMessageはtextに加えて、imageUrlやaltのプロパティを持ちます。
Mapped Types
オブジェクトのキー側のとりうるプロパティが無数にある時やキーの値が変則的である場合にはMapped Typesが利用できます。
Mapped Typesはキー側を[]
で囲って表現します。
以下はキーが全てstring型であるオブジェクト型を定義しています。
type Obj = {
[key: string]: any
}
以下のように[]
内で別の型のキーを全て展開することも可能です。
type Human = {
name: string;
age: number;
birthday: Date;
}
type MaybeHuman = {
[key in keyof Human]?: Human[key]
}
結果、MaybeHumanは以下の型と同義になります
type MaybeHuman = {
name?: string;
age?: number;
birthday?: Date;
}
オブジェクト以外の用途としても利用可能
またtypeはオブジェクトのために用意されたものではありません。stringやnumber, anyなどありとあらゆる型を定義しておくことができます。
例えば以下のようにとりあえず型が決まってないので仕方なくanyにしておき、後で直すという意味合いを込めてTODO
と名付けておくといったユースケースもありそうです。
type TODO = any;
const a: TODO = {
name: "daigo",
age: 30,
}
また、'OK'か'NG'のいずれかの文字列が当てはまる場合の型もtypeを使って表現できます。
ORの表現に|
を使います。
type Judge = 'OK' | 'NG'
interface
次はinterfaceを見ていきます。
interface Human {
name: string;
age: number;
birthday: Date;
}
let man: Human;
一件typeとほとんど変わらないように見えますが、interfaceはどの型も表現できるTypeとは違い、Classもしくはオブジェクトのために用意された仕組みになります。
オプショナル
存在してもしなくてもどちらでもいいプロパティはtypeの時と同じように?で表現できます。
interface Human {
name: string;
age: number;
birthday?: Date;
}
extendsを利用したinterface同士の組み合わせ
interfaceでは&ではなくextendsを使ってinterface同士を組み合わせられます。
interface TextMessage {
text: string;
}
interface ImageMessage {
imageUrl: string;
alt: string;
}
interface Message extends TextMessage, ImageMessage {
}
interfaceの上書き
以下のように同じinterfaceを2回定義すると上のinterfaceを下のinterfaceが上書きする形になります。
interface Message {
text: string;
}
interface Message {
imageUrl: string;
alt: string;
}
結果として以下のinterfaceと同義になります。
interface Message {
text: string;
imageUrl: string;
alt: string;
}
クラスのinterfaceとして利用
またinterfaceはオブジェクトだけではなく以下のようにクラスの型を定義するinterfaceとして利用することもできます。 クラスが正しくinterfaceの型と一致しているかをチェックするために以下のようにクラスに対してimplementsを利用します。
interface ICar {
speed: number;
x: number;
run(): void;
}
class Car implements ICar {
speed: number;
x: number;
constructor(speed: number) {
this.speed = speed;
this.x = 0;
}
run() {
this.x += this.speed;
}
}
まとめ
typeとinterfaceは似ているようで、typeにあってinterfaceにはない機能、またその逆のケースがあることがわかりましたね。 また、例え同じようなユースケースであったとしても、所属しているチームやコントリビュートしているプロジェクトによっても考え方が違うのでそのチームのルールに従ってtypeとinterfaceを使い分けましょう。
著者のおすすめは普段はtypeを使い、classのimplementsの時にだけinterfaceを使うことです。 class以外の用途ではinterfaceでできることのほとんどはtypeでできますし、さらにinterfaceでは表現できないMapped Typesなど多くの機能がtypeに備わっているためです。