2022年4月6日 • ☕️ 3 min read

先日チームメンバーと「TypeScriptのinterfaceとtypeのどっちが好み」を話しました。個人的に安全派なので、interfaceを多く採用しています。今日interfaceとtypeを比較してみようと思います。

declaration merge

  1. interfaceではdeclaration mergeができます。
Copy
interface Car {
  price: number;
}

interface Car {
  weight: number;
}

 /**
  * interface will be merged:
  *
  * interface Car {
  *  price: number;
  *  weight: number;
  * }
  */
const car: Car = {
    price: 10000,
    weight: 1.5
};
  1. typeではdeclaration mergeができません。
Copy
type Car2 = {
  price: number;
}

type Car2 = {
  weight: number;
}

/**
 * Compile error: Duplicate identifier 'Car2'
 */

TypeScript Playgroundで確認へ

extends vs intersection

  1. interfaceとtypeはどちらでも拡張できます。
Copy
interface Car {
  price: number;
}

interface SportsCar extends Car {
  maxSpeed: number;
}
Copy
type Car2 = {
  price: number;
}

type SportsCar2 = Car2 & {
  maxSpeed: number;
}

ただし、下記の2つの違いがあります。

nested objectのextends

  1. interfaceでは、親のobjectのものをもう一度定義する必要があります。
Copy
interface Car {
  price: number;
  status: {
    isNew: boolean;
  }
}

interface SportsCar extends Car {
  maxSpeed: number;
  status: {
    isNew: boolean; // !we have to define the original isNew in status if we want to extends it.
    sold: boolean;
  }
}
  1. typeでは、自動マージなので、親のものを再定義する必要がありません。
Copy
type Car2 = {
  price: number;
  status: {
    isNew: boolean;
  }
}

// original isNew is not necessary.
type SportsCar2 = Car2 & {
  maxSpeed: number;
  status: {
    sold: boolean;
  }
}

TypeScript Playgroundで確認へ

定義が重複した場合

  1. interfaceなら、定義が重複した場合はエラーになります。
Copy
interface Car {
  price: number;
}

interface SportsCar extends Car {
  price: string;
}

/**
 * Compile error: Types of property 'price' are incompatible
 */
  1. typeなら、定義が重複した場合はエラーになりません。ただし、マージされると、neverになってしまう可能性があります。
Copy
type Car2 = {
  price: number;
}

type SportsCar2 = Car2 & {
  price: string;
}

/**
  * No error, but price will become `never`
  */

const sportsCar2: SportsCar2 = {
  price: "1"
}
/* Compile error: Type 'string' is not assignable to type 'never'. */

TypeScript Playgroundで確認へ

performanceについて

パフォーマンス的に、TypeScriptのオフィシャルサイトにintersectionsよりinterface(extends)を使ったほうがいいよと書いてあります。

https://github.com/microsoft/TypeScript/wiki/Performance#preferring-interfaces-over-intersections

整理

  • typeのintersectionsは、できるだけエラーを出さないようにしてあるので、便利です。ただ、間違いましたらコンパイルエラーにならず、型自身がneverになってしまうケースがあるので、気を付けたほうがいいです。
  • パフォーマンスについて、interfaceのextendsのほうがいいですが、普段の開発にはおそらく気にしないレベルだと思います。

union

  1. interfaceでunionを定義できません。

  2. typeならできます。

Copy
type CarType = "car" | "sportsCar" | "suv";

プリミティブのエイリアス

  1. interfaceではできません。

  2. typeならできます。

Copy
type MyString = string;

(4.2前)エラーになった時型の名前の表示

  1. interfaceでは、ちゃんと型の名前が表示されます。
Copy
interface Mammal {
    name: string
}

function echoMammal(m: Mammal) {
    console.log(m.name)
}

// e.g. The error below will always use the name Mammal 
// to refer to the type which is expected:
echoMammal({  name: 12343 })
  1. typeでは、トランスフォームされたものであれば表示されません。
Copy
// But when a a type has been transformed, for example via this
// Omit then the error message will show the resulting type
// and not the name

type Arachnid = Omit<{ name: string, legs: 8 }, 'legs'> 

function echoSpider(l: Arachnid) {
    console.log(l.name)
}

echoSpider({ name: 12345, legs: 8})

/**
 * The expected type comes from property 'name' which is declared here on type 'Pick<{ name: string; legs: 8; }, "name">'
 * /

TypeScript Playgroundで確認へ

完了

interfaceが安全派、typeが利便性派な気がします。個人的に、interface派となります。4.2以後安全派じゃなければ、デカいプロジェクト(コンパイルパフォーマンス重視)以外、どちら使っても問題ないと思います。


関連投稿

Next.js + material-ui + styled-componentsの型安全のテーマカスタマイズ

2022年1月16日

ThunderMiracle

Blog part of ThunderMiracle.com