mosya

TypeScriptにおけるtypeとinterfaceの違い

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 に備わっているためです。

Authored by

筆者の写真

Godai@steelydylan

Webサービスを作るのが好きなWebエンジニア。子供が産まれたことをきっかけに独立し法人化。サービス開発が大好き。
好きな言語はTypeScript。

サイトの模写でプロを目指す
オンライン学習サービスを作りました!

詳しくはこちら
mosya

mosyaはオンラインでWeb制作を自学習できるサービスです。現役エンジニアが作成した豊富なカリキュラムに沿って学習を進めましょう。

© 2023 - mosya. All rights reserved.