📑

TypeScript InterfaceとTypeの比較

2021/03/25に公開

先日、TypeScriptをプロジェクトに導入しようとしていた時、InterfaceTypeの使い方と用途が紛らわしいなーと思いました。
これを機にInterfaceとTypeの差について比較して整理したいと思います。

基本使い方

基本的な使い方は以下の通りです。

// Inferface
interface IPerson {
  name: string
  age: number
}

const james: IPerson = {
  name: 'james',
  age: 24,
}
// Type
type PersonType = {
  name: string
  age: number
}

const jane: PersonType = {
  name: 'jane',
  age: 26,
}

違うところ

拡張

まず、拡張する方法においてInterfaceはextendsを使って、Typeは&(Intersection、交差型)を使って拡張ができます。

// Interface
interface IPerson {
  name: string
  age: number
}

interface Programmer extends IPerson {
  language: string
}
// Type
type PersonType = {
  name: string
  age: number
}

type ProgrammerType = PersonType & {
  job: string
}

宣言的な拡張

Interfaceで出来る機能の大部分はTypeでも出来ます。ただ、新しいプロパティの追加に関してはInterfaceは同じ名でまた宣言することが可能ですが、Typeの場合は不可能です。

// Interface
interface Window {
  title: string
}

interface Window {
  ts: TypeScriptAPI
}

const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});
// Type
type Window = {
  title: string
}

type Window = {
  ts: TypeScriptAPI
}

// Error: Duplicate identifier 'Window'.

Object以外の場合

Typeの場合、Object以外の型でも使えますが、InterfaceはObjectのみ使えます。

// Interface
interface IFoo {
  value: string
}

// NG 
interface IFoo extends string {}
// Type
type FooType = {
  value: string
}

// OK
type FooOnlyString = string
type FooTypeNumber = number

Computed Property

プロパティとして計算された値とか、動的な値を使いたい場合役に立つComputed PropertyはTypeだけ使えます。

type names = 'firstName' | 'lastName'

type NameType = {
  [key in names]: string
}

const person: NameType = { firstName: 'john', lastName: 'smith' }

interface IName {
  // NG
  [key in names]: string
}

パフォーマンス

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

こちらの説明を基にしてInterfaceがTypeよりパフォーマンス上のメリットがある所について紹介します。

拡張時のマージング

Interfaces create a single flat object type that detects property conflicts, which are usually important to resolve! Intersections on the other hand just recursively merge properties, and in some cases produce never.

  • InterfaceはObject型のタイプを作るためのものなので拡張(extends)時にも単純にマージしたらOKです。
  • 一方Typeの場合は再帰的に巡回(reculsive loop)しながらプロパティをマージしていきます。
  • TypeはObject型以外にプリミティ型も設定できるので衝突(Conflict)が発生してneverになる場合があります。
type okType = { a: 1 } & { b: 2 }
type ngType = { a: 1, b: 2 } & { b: 3 } // type ngType = never

const t1: okType = { a: 1, b: 2 } // OK
const t2: ngType = { a: 1, b: 3 } // Type 'number' is not assignable to type 'never'.(2322)
const t3: ngType = { a: 1, b: 2 } // Type 'number' is not assignable to type 'never'.(2322)
  • それでTypeの拡張のために&を使う時には注意が必要です。Object型を宣言する場合はInterfaceがマシです。

キャッシング

Type relationships between interfaces are also cached, as opposed to intersection types as a whole.

  • Interfaceの場合、拡張時にキャッシングされますが、Typeはされないです。

コンパイル時

A final noteworthy difference is that when checking against a target intersection type, every constituent is checked before checking against the "effective"/"flattened" type.

  • Type の場合、拡張時に、拡張自体の有効性を判断する前に、全てのプロパティに対するタイプをチェックするため、Interface に比べてパフォーマンスが良くないです。

まとめ

プロジェクトを進めるいくことに当たって、それぞれの特徴はもちろんありますが、ほとんどのところで機能は一緒なのでInterfaceとTypeの中で何を使おうか統一する必要はあると思います。個人的には上記にもあるタイプの拡張やパフォーマンス上のメリットで見るとInterfaceを主に使った方が良いかと思います。

番外

https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#interfaces

Prior to TypeScript version 4.2, type alias names may appear in error messages, sometimes in place of the equivalent anonymous type (which may or may not be desirable). Interfaces will always be named in error messages.

TypeScript 4.2以前のバージョンには、Type の場合、時々anonymous typeと宣言される場合があり、エラーメッセージでInterfaceよりデメリットだった部分がありましたが、TypeScript 4.2からはTypeの場合にもエラーがよく表示されるようになりました。

Discussion