TypeScriptのInterfaceとTypeどちらも用途は同じような感じで使えてしまい、はっきりとした違いが分からなかったので調べてみました。
クラスやオブジェクトの構造を宣言するためのもの
interface MapPoint {
latitude: string;
longitude: string;
}
ある型に対する別名をつけるためのもの
type MapPoint = {
latitude: string;
longitude: string;
}
公式ドキュメントや色んな記事を参考にしてみた結果、ひとまずこんな感じに結論づけてみました。(後でまた変わるかもしれません)
機能として優れているのはTypeのほうなので基本はInterfaceよりtypeを積極的に使っていけばいいかと思います。
また、プロジェクト全体をオブジェクト思考的に最適化する場合も、Interfaceとtypeのどちらもつかえちゃうが、これもまたtypeのほうを使っていく方向性でいいかと思います。
①用途・目的について
Interface | Type |
クラスやオブジェクトの構造を宣言 | ある型に対する別名をつける |
②継承について
Interface | Type |
可能 | 可能(交差型) |
// interfaceの継承
interface MapPoint {
latitude: string;
longitude: string;
}
interface PointPlace extends MapPoint {
address: string;
}
// typeの継承っぽいやつ(交差型)
type MapPoint = {
latitude: string;
longitude: string;
}
type PointPlace = MapPoint & {
address: string;
}
③Classへのimplementについて
Interface | Type |
可能 | 可能 |
// interfaceのimplements
interface MapPoint {
latitude: string;
longitude: string;
}
interface PointPlace extends MapPoint {
address: string;
}
class OfficePlace implements PointPlace {
latitude: string;
longitude: string;
address: string;
name: string;
constructor() {
this.latitude = '';
this.longitude = '';
this.address = '';
this.name = '';
}
}
// typeのinplements
type MapPoint = {
latitude: string;
longitude: string;
}
type PointPlace = MapPoint & {
address: string;
}
class OfficePlace implements PointPlace {
latitude: string;
longitude: string;
address: string;
name: string;
constructor() {
this.latitude = '';
this.longitude = '';
this.address = '';
this.name = '';
}
}
let myOffice: OfficePlace = new OfficePlace();
myOffice.latitude = "35.6809591";
myOffice.longitude = "139.767125";
④同名要素のマージ
一度定義したInterfaceの後に再定義を行い、最初のInterfaceに後で追記したInterfaceの型を統合(マージ)することができる。
(個人的にはこれはメリットというよりバグなんかを生むきっかけになりそうな予感)
Interface | Type |
可能 | × |
// interfaceの同名要素例
interface MapPoint {
latitude: string;
longitude: string;
}
// OK(MapPointで同名定義)
interface MapPoint {
address: string;
}
// OK
const placeA: MapPoint = {latitude: '35.6809591', longitude: '139.767125', address: '東京都'}
// NG
const placeB: MapPoint = {latitude: '35.6809591', longitude: '139.767125'}
// プロパティ 'address' は型 '{ latitude: string; longitude: string; }' にありませんが、型 'MapPoint' では必須です
// typeの同名要素例
type MapPoint = {
latitude: string;
longitude: string;
}
// NG (識別子 'MapPoint' が重複しています)
type MapPoint = {
address: string;
}
インデックスシグネチャ
オブジェクトを作成する際に、中身のデータ名を抽象的に書きたい場合に使い、プロパティへの添え字アクセスに対する型情報を定義します。
//インデックスシグネチャを定義(keyはstring)
interface StrOptions { [index: string]: string | number; }
let strObj: StrOptions = {};
strObj.name = 'スクリプト太郎';
strObj.age = 100;
//インデックスシグネチャを定義(keyはnumber)
interface NumOptions { [index: number]: string | number; }
let numObj: NumOptions = {};
numObj[0] = 'スクリプト太郎';
numObj[1] = 'スクリプト次郎';
numObj[3] = 'スクリプト三郎';
//typeはエラー
type StrOptions { [index: string]: string | number; }
Mapped Types
Mapped typeは主にユニオン型と組み合わせて使い、インデックス型と同じようにキーの制約として使用することができます。
type Languages = 'en' | 'ja' |'fr' | 'it' | 'es';
type SupportLanguages = { [key in Languages]: boolean; }
// interface はエラー
interface SupportLanguages2 = { [key in Languages]: boolean; }
交差型
type Point = { latitude: string, longitude: string }
type Location = Point & { address: string }
共用体型
いずれかの型を満たすことができる構造を定義
// numberかstringの型
type Point = number | string
function get(arg: Point) {
if (typeof arg === 'number') {
//argがnumberのケース
console.log(arg);
} else {
//argがstringのケース
console.log(arg.toLowerCase());
}
}
type内の定義していないプロパティ以外の存在について
declare function getX(obj: { [key: string]: number }): string;
declare function getXX(obj: { price: number, tax: number }): string;
type Price = { price: number, tax: number }
interface IPrice { price: number, tax: number }
const priceA = { price: 100, tax: 10 }
getX(priceA) // OK
getXX(priceA) // OK
const priceB = { price: 100, tax: 10, discount: 20 }
getX(priceB) // OK
getXX(priceB) // OK
const priceC: Price = { price: 100, tax: 10}
getX(priceC) // OK
getXX(priceC) // OK
const priceD: IPrice = { price: 100, tax: 10 }
getX(priceD) // Error!
getXX(priceD) // OK