TypeScriptの型宣言について調べているとinterfaceによる宣言とtypeによる宣言が出てきます。
interfaceってなんだっけ?とよくなったのでここらでキッチリ倒しておきたいと思い記事に起こします!
今後interfaceとtype宣言の違いを理解して、今後どちらを使っていいのか迷わないようになるための記事です!
TypeScriptのinterfaceとtypeそれぞれの特徴
サバイバルTypeScriptの参考コードがわかりやすかったのでそちらを抜粋して記事にします!
interfaceと型エイリアスの特徴早見表
特徴が表にまとまってました。
具体的にコードを参考に調べていきます!
| 内容 | インターフェイス | 型エイリアス |
|---|---|---|
| 継承 | 可能 | 不可ただし交差型で表現は可能 |
| 継承による上書き | 上書きまたはエラー | フィールド毎に交差型が計算される |
| 同名のものを宣言 | 定義がマージされる | エラー |
| Mapped Types | 使用不可 | 使用可能 |
TypeScriptの継承とは?
interfaceで定義するとinterfaceで定義された型と型エイリアスで定義された型両方をextendsを利用することによって継承できます。
interface Animal {
name: string;
}
type Creature = {
dna: string;
};
interface Dog extends Animal, Creature {
dogType: string;
}
型エイリアスは継承できませんが、交差型(&)を使用して継承と同じようなことができます!
type Animal = {
name: string;
};
type Creature = {
dna: string;
};
type Dog = Animal &
Creature & {
dogType: string;
};
TypeScriptの継承による上書き
interfaceでプロパティをオーバーライドすると継承元のプロパティが上書きされます。
interface Animal {
name: any;
price: {
yen: number;
};
legCount: number;
}
// Animalを継承してnameとpriceのyenを上書きします
interface Dog extends Animal {
name: string; // 上書き
price: {
yen: number;
dollar: number;
};
}
// 最終的なDogの定義
// interfaceでオーバーライドできない場合はエラーになります
interface Dog {
name: string;
price: {
yen: number;
dollar: number;
};
legCount: number;
}
ただ、number型をstring型`ではオーバーライドできないなど、元の型に代入できる型であることが条件です。
一度定義した型を別のところでオーバーライドすることは、予期せぬ不具合を招く恐れがあるのでやめた方が良いのではないかと考えております…。
型エイリアスの場合はinterfaceのように上書きはできません。
type Animal = {
name: number;
price: {
yen: number;
dollar: number;
};
};
type Dog = Animal & {
name: string;
price: {
yen: number;
euro: number;
};
};
// 最終的なDogの定義
// interfaceのようにエラーは出ませんがnever型になります
type Dog = {
name: never;
price: {
yen: number;
dollar: number;
euro: number;
};
};
TypeScriptで同名のものを宣言
型エイリアスは同名では複数回定義できずコンパイルエラーになります。
interfaceだと同名の定義が可能です。
interface SameNameInterfaceIsAllowed {
myField: string;
sameNameSameTypeIsAllowed: number;
sameNameDifferentTypeIsNotAllowed: string;
}
interface SameNameInterfaceIsAllowed {
newField: string;
sameNameSameTypeIsAllowed: number;
}
interface SameNameInterfaceIsAllowed {
// 同名のフィールドで型の定義が間違っている場合エラーになります。
sameNameDifferentTypeIsNotAllowed: number;
}
Mapped Types
「Mapped Types」は型のキーを動的に指定できる仕組みで、型エイリアスでのみ利用できます。
type SystemSupportLanguage = 'en' | 'fr' | 'it' | 'es';
type Butterfly = {
[key in SystemSupportLanguage]: string;
};
// 結果
type Butterfly = {
en: string;
fr: string;
it: string;
es: string;
}
【まとめ】interfaceとtypeどっち使えばいいの?
結論から言うと正解はないです…が個人としては**型エイリアスを利用するのがいいのでは?**と考えています。
その理由は
-
interfaceはDeclaration mergingされる -
interfaceではunion型やtuple型は定義できない
Declaration mergingという機能
サバイバルTypeSTypeScriptで挙げられていた複数定義した場合にくっつけてくれる機能のことです!
外部のライブラリの型を拡張する時には良いですが、グローバルに定義されている型があった場合に意図せず拡張してしまうケースもあると思います…。
union型やtuple型を使う場合は型エイリアス
使う必要がないならinterfaceを利用するで良いですが、使わないのは少し厳しいのかも…と実装経験が少ないながら感じます。
interfaceで型を定義することのメリットがないというわけではありませんが、
型エイリアスの定義で全てをカバーできるなら型エリアスで統一して定義した方がいいのかな…というのが現時点での結論です。