mosya

TypeScriptにおけるtypeとinterfaceの違い

TypeScript にはオブジェクトの型を表現する手段として type を使う方法と interface を使う方法があります。
どちらも機能としてとても似たようなところがあるのですがいくつかの違いもあります。
早速一つずつ見ていきましょう。

type

オブジェクトの型は type を使うことで以下のように表現することができます。

type type Human = {
    name: string;
    age: number;
    birthday: Date;
}Human = {
  name: stringname: string;
  age: numberage: number;
  birthday: Datebirthday: Date;
};

let let man: Humanman: type Human = {
    name: string;
    age: number;
    birthday: Date;
}Human;

オプショナル

オブジェクトのプロパティに値があってもなくてもどちらでもいいよという場合は以下のように?をつけます

type type Human = {
    name: string;
    age: number;
    birthday?: Date | undefined;
}Human = {
  name: stringname: string;
  age: numberage: number;
  birthday?: Date | undefinedbirthday?: Date;
};

const const human: Humanhuman: type Human = {
    name: string;
    age: number;
    birthday?: Date | undefined;
}Human = {
  name: stringname: "daigo",
  age: numberage: 30,
  // birthdayがなくてもOK
};

&演算子を使った Type 同士の合体

また以下のように型と型同士を&を使って組み合わせて使うこともできます。

type type TextMessage = {
    text: string;
}TextMessage = {
  text: stringtext: string;
};

type type ImageMessage = {
    imageUrl: string;
    alt: string;
}ImageMessage = {
  imageUrl: stringimageUrl: string;
  alt: stringalt: string;
};

type type Message = TextMessage & ImageMessageMessage = type TextMessage = {
    text: string;
}TextMessage &
  type ImageMessage = {
    imageUrl: string;
    alt: string;
}ImageMessage;

この時型の Message は text に加えて、imageUrl や alt のプロパティを持ちます。

Mapped Types

オブジェクトのキー側のとりうるプロパティが無数にある時やキーの値が変則的である場合には Mapped Types が利用できます。
Mapped Types はキー側を[]で囲って表現します。

以下はキーが全て string 型であるオブジェクト型を定義しています。

type type Obj = {
    [key: string]: any;
}Obj = {
  [key: stringkey: string]: any;
};

以下のように[]内で別の型のキーを全て展開することも可能です。

type type Human = {
    name: string;
    age: number;
    birthday: Date;
}Human = {
  name: stringname: string;
  age: numberage: number;
  birthday: Datebirthday: Date;
};

type type MaybeHuman = {
    name?: string | undefined;
    age?: number | undefined;
    birthday?: Date | undefined;
}MaybeHuman = {
  [function (type parameter) keykey in keyof type Human = {
    name: string;
    age: number;
    birthday: Date;
}Human]?: type Human = {
    name: string;
    age: number;
    birthday: Date;
}Human[function (type parameter) keykey];
};

結果、MaybeHuman は以下の型と同義になります

type type MaybeHuman = {
    name?: string | undefined;
    age?: number | undefined;
    birthday?: Date | undefined;
}MaybeHuman = {
  name?: string | undefinedname?: string;
  age?: number | undefinedage?: number;
  birthday?: Date | undefinedbirthday?: Date;
};

オブジェクト以外の用途としても利用可能

また type はオブジェクトのために用意されたものではありません。string や number, any などありとあらゆる型を定義しておくことができます。
例えば以下のようにとりあえず型が決まってないので仕方なく any にしておき、後で直すという意味合いを込めてTODOと名付けておくといったユースケースもありそうです。

type type TODO = anyTODO = any;

const const a: anya: type TODO = anyTODO = {
  name: stringname: "daigo",
  age: numberage: 30,
};

また、'OK'か'NG'のいずれかの文字列が当てはまる場合の型も type を使って表現できます。
OR の表現に|を使います。

type type Judge = "OK" | "NG"Judge = "OK" | "NG";

interface

次は interface を見ていきます。

interface Human {
  Human.name: stringname: string;
  Human.age: numberage: number;
  Human.birthday: Datebirthday: Date;
}

let let man: Humanman: Human;

一件 type とほとんど変わらないように見えますが、interface はどの型も表現できる Type とは違い、Class もしくはオブジェクトのために用意された仕組みになります。

オプショナル

存在してもしなくてもどちらでもいいプロパティは type の時と同じように?で表現できます。

interface Human {
  Human.name: stringname: string;
  Human.age: numberage: number;
  Human.birthday?: Date | undefinedbirthday?: Date;
}

extends を利用した interface 同士の組み合わせ

interface では&ではなく extends を使って interface 同士を組み合わせられます。

interface TextMessage {
  TextMessage.text: stringtext: string;
}

interface ImageMessage {
  ImageMessage.imageUrl: stringimageUrl: string;
  ImageMessage.alt: stringalt: string;
}

interface Message
  extends TextMessage,
    ImageMessage {}

interface の上書き

以下のように同じ interface を 2 回定義すると上の interface を下の interface が上書きする形になります。

interface Message {
  Message.text: stringtext: string;
}

interface Message {
  Message.imageUrl: stringimageUrl: string;
  Message.alt: stringalt: string;
}

結果として以下の interface と同義になります。

interface Message {
  Message.text: stringtext: string;
  Message.imageUrl: stringimageUrl: string;
  Message.alt: stringalt: string;
}

クラスの interface として利用

また interface はオブジェクトだけではなく以下のようにクラスの型を定義する interface として利用することもできます。
クラスが正しく interface の型と一致しているかをチェックするために以下のようにクラスに対して implements を利用します。

interface ICar {
  ICar.speed: numberspeed: number;
  ICar.x: numberx: number;
  ICar.run(): voidrun(): void;
}

class class CarCar implements ICar {
  Car.speed: numberspeed: number;
  Car.x: numberx: number;
  constructor(speed: numberspeed: number) {
    this.Car.speed: numberspeed = speed: numberspeed;
    this.Car.x: numberx = 0;
  }
  Car.run(): voidrun() {
    this.Car.x: numberx += this.Car.speed: numberspeed;
  }
}

まとめ

type と interface は似ているようで、type にあって interface にはない機能、またその逆のケースがあることがわかりましたね。
また、例え同じようなユースケースであったとしても、所属しているチームやコントリビュートしているプロジェクトによっても考え方が違うのでそのチームのルールに従って type と interface を使い分けましょう。

著者のおすすめは普段は type を使い、class の implements の時にだけ interface を使うことです。
class 以外の用途では interface でできることのほとんどは type でできますし、さらに interface では表現できない Mapped Types など多くの機能が type に備わっているためです。

Authored by

筆者の写真

Godai@steelydylan

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

ReactやTypeScriptなどの周辺技術が学べる
オンライン学習サービスを作りました!

詳しくはこちら
mosya

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

© 2023 - mosya. All rights reserved.