名前付き型を interface と type で定義するときの留意事項

# TypeScriptの `interface` と `type` の違い

TypeScriptを採用する最大の利点は、強力な型システムにあります。
型の名前を定義する `type` と `interface` の2つのオプションには、類似点が多いです。

プロジェクトでどちらかを使う上で迷いが出てしまうことがあるので、これらのオプションについて特性を調べました。 

TypeScript v3.7.2 の [Playground](https://www.typescriptlang.org/play/) で検証しています。
 
## TL;DR

- `type` と `interface` の類似点と違いを理解します。
- いずれかの構文を使用して同じ型を記述する方法を知る。
- プロジェクトでどちらを使用するかを決める際には、確立されたスタイルと拡張性が有用かどうかを検討する必要がある。

## 類似点と違い

TypeScriptで型の名前を定義する。

```ts
type TState = { name : string ; capital : string ; } 
interface IState { name : string ; capital : string ; } 
```

普段どちらを選択しているでしょうか?

これら2つのオプションの境界線は、長年にわたってますます曖昧になってきています。多くの場面で両方とも利用できます。

型は互いにほとんど区別できません。

未定義プロパティを使用したときに発生するエラー内容は、 `IState` 、 `TState`で同様です。 

```ts
const wyoming : TState = { 
	name : 'Wyoming' , 
	capital : 'Cheyenne' , 
	population : 500000 // ~~~~~~~~~~~~~~~~~~ Type ... is not assignable to type 'TState' // Object literal may only specify known properties, and // 'population' does not exist in type 'TState' 
};
```

`type` と `interface` でどちらもkeyの型も指定できます。

```ts
type TDict = { [ key : string ] : string };
interface IDict { [ key : string ] : string ; } 
```

関数の型も定義できます。

```ts
type TFn = ( x : number ) => string ;
interface IFn { ( x : number ) : string ; } 
const toStrT : TFn = x => '' + x ; // OK
const toStrI : IFn = x => '' + x ; // OK 
```

`interface` は型を拡張できます。
また、`type` は `interface` を拡張できます。

```ts
interface IStateWithPop extends TState { 
	population : number ; 
} 
type TStateWithPop = IState & { population : number ; }; 
```

繰り返しますが、これらの型は同じです。

注意点は、`interface` はunion型のような複雑な型を拡張できないことです。 

これを行うには、`type` と `＆` を使用する必要があります。 

`class` は、`interface` または `type` のいずれかを実装できます。

```ts
class StateT implements TState { 
	name : string = '' ; 
	capital : string = '' ; 
} 
class StateI implements IState { 
	name : string = '' ;
	capital : string = '' ; 
} 
```

このように類似点をあげましたが、違いはどうでしょう？ 

union type がありますが、union interface はありません。

```ts
type AorB = 'a' | 'b' ; 
```

入力変数と出力変数に別々のタイプがあり、名前から変数へのマッピングがある場合は、union型で拡張すると便利です。

```ts
type Input = { /* ... */ }; 
type Output = { /* ... */ }; 
interface VariableMap { 
	[ name : string ] : Input | Output ; 
} 
```

次に、変数に名前を付ける型が必要になる場合があります。

 ```ts
 type NamedVariable = ( Input | Output ) & { name : string }; 
 ```
 
 `interface` でこの型を表現することはできません。 
 
 `type` は `interface` よりも機能が豊富です。 
 
 union 型にすることもでき、map 型や条件付き型などのより高度な機能を利用することもできます。 
 
 また、tuple 型 と配列型をよりシンプルに表現できます。

```ts
type Pair = [ number , number ]; 
type StringList = string []; 
type NamedNums = [ string , ... number []]; 
```

`interface` を使用して tuple 型 のようなものは表現できます。

```ts
interface Tuple { 
	0 : number ; 
	1 : number ; 
	length : 2 ; 
} 
const t : Tuple = [ 10 , 20 ]; // OK 
```

しかし、これは厄介なことがあります。

`concat` のような全ての tuple メソッドを削除してしまうので、`type` を使用する方が良いです。 

ただし、`interface` には、`type` には無いいくつかの機能があります。 

そのひとつは、`interface` を拡張できることです。 

`IState` の例に戻ると、別の方法で人口フィールドを追加できます。

```ts
interface IState { name : string ; capital : string ; } 
interface IState { population : number ; } 
const wyoming : IState = { 
	name : 'Wyoming' , 
	capital : 'Cheyenne' , 
	population : 500000 
}; // OK 
```

これは、*declaration merging* と呼ばれるもので、見慣れないものであった場合なら驚くことでしょう。

これは主に `.d.ts` ファイルで使用されます。

作成する場合は、標準に従って `interface` を使用します。

目的は、利用者が入力する必要がある型宣言にギャップがあるかもしれないので、`interface` を使って型宣言を行いうことです。

TypeScript はマージを使用して、JavaScript の標準ライブラリのさまざまなバージョンのさまざまな型を取得します。

たとえば、Array interface は `lib.es5.d.ts` で定義されています。

デフォルトでは、これが全てです。

ただし、`tsconfig.json` の lib エントリに ES2015 を追加すると、TypeScript には `lib.es2015.d.ts` も含まれます。

これには、ES2015 で追加された find などの追加メソッドを備えた別の Array interface が含まれます。

それらは、マージによって他の Array interface に追加されます。

最終的には、正確な配列型を単一取得できます。

マージは `.d.ts` ファイル だけでなく通常のコードでもサポートされており、その副作用には注意しなければいけません。

自分が定義した型に追加されないことが不可欠である場合は、`type` を使用します。

## まとめ

ところで、`type`、`interface` は結局どちらを使えば良いでしょうか?

複雑な型の場合、選択肢はありません。

`type` を使用する必要があります。

しかし、どちらの方法でも表現できる単純なオブジェクト型はどうでしょうか。

このとき、一貫性と拡張性を考慮する必要があります。

一貫して `interface` を使用するコードベースで作業していますか？

それなら、`interface`に統一します。

スタイルが確立されていないプロジェクトの場合、拡張性について検討する必要があります。

API の型宣言を公開している場合は、API が変更されたときに、利用者が `interface` を介して新しいフィールドにマージできるようになると便利なので `interface` を使用します。

ただし、プロジェクトで内部的に使用される型の場合、宣言のマージによって想定外な型になっている可能性があるので `type` を優先した方が良さそうです。

> 出典: Dan Vanderkam(著), 「Effective TypeScript」, O'Reilly Media; 1版 (2019/10/17), Item 13: Know the Differences Between type and interface