先日チームメンバーと「TypeScriptのinterfaceとtypeのどっちが好み」を話しました。個人的に安全派なので、interfaceを多く採用しています。今日interfaceとtypeを比較してみようと思います。
declaration merge
- interfaceではdeclaration mergeができます。
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
};
- typeではdeclaration mergeができません。
type Car2 = {
price: number;
}
type Car2 = {
weight: number;
}
/**
* Compile error: Duplicate identifier 'Car2'
*/
extends vs intersection
- interfaceとtypeはどちらでも拡張できます。
interface Car {
price: number;
}
interface SportsCar extends Car {
maxSpeed: number;
}
type Car2 = {
price: number;
}
type SportsCar2 = Car2 & {
maxSpeed: number;
}
ただし、下記の2つの違いがあります。
nested objectのextends
- interfaceでは、親のobjectのものをもう一度定義する必要があります。
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;
}
}
- typeでは、自動マージなので、親のものを再定義する必要がありません。
type Car2 = {
price: number;
status: {
isNew: boolean;
}
}
// original isNew is not necessary.
type SportsCar2 = Car2 & {
maxSpeed: number;
status: {
sold: boolean;
}
}
定義が重複した場合
- interfaceなら、定義が重複した場合はエラーになります。
interface Car {
price: number;
}
interface SportsCar extends Car {
price: string;
}
/**
* Compile error: Types of property 'price' are incompatible
*/
- typeなら、定義が重複した場合はエラーになりません。ただし、マージされると、
never
になってしまう可能性があります。
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'. */
performanceについて
パフォーマンス的に、TypeScriptのオフィシャルサイトにintersections
よりinterface
(extends)を使ったほうがいいよと書いてあります。
https://github.com/microsoft/TypeScript/wiki/Performance#preferring-interfaces-over-intersections
整理
- typeのintersectionsは、できるだけエラーを出さないようにしてあるので、便利です。ただ、間違いましたらコンパイルエラーにならず、型自身が
never
になってしまうケースがあるので、気を付けたほうがいいです。 - パフォーマンスについて、interfaceのextendsのほうがいいですが、普段の開発にはおそらく気にしないレベルだと思います。
union
-
interfaceでunionを定義できません。
-
typeならできます。
type CarType = "car" | "sportsCar" | "suv";
プリミティブのエイリアス
-
interfaceではできません。
-
typeならできます。
type MyString = string;
(4.2前)エラーになった時型の名前の表示
- interfaceでは、ちゃんと型の名前が表示されます。
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 })
- typeでは、トランスフォームされたものであれば表示されません。
// 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">'
* /
完了
interfaceが安全派、typeが利便性派な気がします。個人的に、interface派となります。4.2以後安全派じゃなければ、デカいプロジェクト(コンパイルパフォーマンス重視)以外、どちら使っても問題ないと思います。