Typescript(インターフェース)

はじめに

今回は、型エイリアスと同様にインターフェースという、型に名前をつけるための方法についてです。また今回の記事は合併型や交差型の理解も必要となるため、前に書いた記事Typescript(合併型と交差型) - 駆け足エンジニアの記録を参考にください。
エイリアスとインターフェースは、ほぼ同じ事を行うための構文ですが、小さな違いがいくつかあります。まずは型エイリアスとインターフェースの定義方法の違いから見ていきましょう。

インターフェースの定義

//インターフェース
interface Person {
  name: string;
  age: number;
}
//型エイリアス
type Person = {
  name: string,
  age:number
}

ほとんど型エイリアスと同じです。(違いは等号ぐらいです。)また初期値を与えることができないことも共通点の一つです。あくまで型の指定だけです。
用途もシンプルです。

interface Person {
  name: string;
  age: number;
}
let user1: Person = {
  name: "Max",
  age: 20,
};

インターフェースを使いたいところ(変数や引数)で型指定時にインターフェース名(ここではPerson)を記述するだけです。このように一つのインターフェースを作ることによって、何度も型指定時に使いまわしが可能となります。

ここまで型エイリアスとインターフェースの共通点を述べてきました。ほとんど同じ機能をなぜ分ける必要があったのか、それはもちろん違いがあるからです。以下その違いを見ていきましょう。


エイリアスとインターフェースの違い

①インターフェースはオブジェクトの型指定のみ可能

これは大きな違いです。型エイリアスはプリミティブ型、オブジェクト型(関数、配列)あらゆる型(ユニオン型etc...)を定義できます。それに対し、インターフェースではオブジェクトの構造を記述するためだけに使える記法となります。型エイリアスの方がインターフェースよりも定義できる型が多くバリュエーションが豊富です。ただインターフェースはオブジェクト型のみ定義できる分、用途が明白です。これはコードの可読性に繋がります。
一つ例外として、インターフェースでは関数型を定義できます(関数シグネチャと言われています)

//型エイリアスによる関数型
type AddFn = (a: number, b: number) => number;

const add: AddFn = (n1, n2) => {
  return n1 + n2;
};
console.log(add(2, 4)); //6


//インターフェースによる関数型(関数シグネチャ)
interface AddFn {
  (a: number, b: number): number;
}
const add: AddFn = (n1, n2) => {
  return n1 + n2;
};
console.log(add(2, 4)); //6
②インターフェースは継承できる

継承といえばクラス継承が頭に思い浮かびますが、インターフェースもクラス継承同様extendsキーワードを使うことで継承できます。

interface Person {
  name: string;
  age: number;
}
interface Greetable extends Person {
  greet(phrase: string): void;
}

let obj: Greetable = {
  name: "Max",
  age: 20,
  greet(phrase: string) {
    console.log(phrase);
  },
};

エイリアスではできないのかというと、継承めいた実装は交差型(intersection型)を使うことで可能です。

type Person = {
  name: string;
  age: number;
};
type Greetable = {
  greet(phrase: string): void;
};

let obj2: Person & Greetable = {//&で交差型を表す。
  name: "Max",
  age: 20,
  greet(phrase) {
    console.log(phrase);
  },
};

③宣言のマージ

宣言のマージとは、同じ名前を共有する複数の宣言を自動的に宣言するTypescriptの機能です。
以下、インターフェースのに関する宣言のマージを確認しましょう。

interface Person {
  name: string;
}
interface Person {
  age: number;
}
let user: Person = {//Personにはnameとageが必要
  name: "Naruto",
  age: 17,
};

2つ目のPerson というインターフェースの宣言の時点でPerson にはnameプロパティとageプロパティを持ちます。

宣言のマージを型エイリアスを使って書き直そうとするとエラーが起きます。

type Person= {//エラー(識別子 'Person' が重複しています。)
  name: string;
}
type Person= {//エラー(識別子 'Person' が重複しています。)
  age: number;
}
let user: Person = {
  name: "Naruto",
  age: 17,//エラー(オブジェクト リテラルは既知のプロパティのみ指定できます。'age' は型 'Person' に存在しません。)
};
④合併型、交差型について

インターフェースでは合併型、交差型を使うことはできません。つまり、次の型エイリアスによる合併型、交差型の実装をインターフェースで代用する事はできません。

// 合併型
// 引数としてnumberかstringを受け取る
type NumOrStr = number | string;
function logLowerCase(arg: NumOrStr) {
  if (typeof arg === 'number') {
    console.log(arg);
  } else {
    // この時点でargはstring確定
    console.log(arg.toUpperCase());
  }
}

// 交差型
// 引数としてaとbの両方のプロパティを持つオブジェクトを受け取る
type AB = {a: string} & {b: string};
function logAB(arg: AB) {
  console.log(arg.a + arg.b);
}