[TypeScript] インターフェースをマスターしよう！

### はじめに
今回はインターフェースについてまとめました。
オブジェクト指向の核心に迫ってきてますね...

### 【目次】
[まずは基本的な型宣言をマスターしよう！](https://qiita.com/PenPe/items/c189a611b3407b7ceef9)
[オブジェクト型の型宣言をマスターしよう！](https://qiita.com/PenPe/items/440683cc6e5e4ac0ca32)
[関数の型定義をマスターしよう！](https://qiita.com/PenPe/items/bff109c6f83f46d191a7)
[配列をマスターしよう！](https://qiita.com/PenPe/items/7e056eec2cce76a745a9)
[インターフェースをマスターしよう！](https://qiita.com/PenPe/items/f9314aeccb2f23cac382) ←🈁
[クラスを使いこなそう！](https://qiita.com/PenPe/items/c8e6289f386c4db23a6f)
[型修飾子についての理解を深めよう！](https://qiita.com/PenPe/items/7af57861e93830fe9a79)
[ジェネリックをマスターしよう！](https://qiita.com/PenPe/items/fde6a342e746e265d907)
[独自の構文拡張をマスターしよう！](https://qiita.com/PenPe/items/c79cdb5bc4a68a1dfd7f)

---

### インターフェースとは？
インターフェースとは、構造的なデータの型定義に利用できる機能です。

### インターフェースの宣言
```typescript
interface Person {
    name: string,
    age: number
}
```

---

### 型エイリアスとの違い
同じような機能で型エイリアスがありました。

```typescript
type PersonObj = {
    name: string,
    age: number
}
```

インターフェースと型エイリアスの違いは以下の通りです。
- クラスの構造的な型定義はインターフェースのみ可
- インターフェースは拡張、マージが可
- インターフェースは型エイリアスより高速（内部にキャッシュ）

上記理由から、**インターフェースが型エイリアスより好んで利用される**ことが多いです。

---

### 代表的な機能

インターフェースの機能について、代表的なものをいくつかご紹介します。

#### :memo: オプションプロパティ
オブジェクト型の記事でもご紹介しましたが、プロパティ名の後に `?` をつけることで省略可能なオプションを定義することができます。

```typescript
interface Person {
    name: string,
    age: number
}

interface PersonOptionalAge {
    name: string,
    age?: number
}

// NG: Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.
let taro: Person = {
    name: "Taro",
}

// OK
let haewon: PersonOptionalAge = {
    name: "Haewon"
}
```

#### :memo: 読み取り専用プロパティ
インターフェースではプロパティへの書き込みを禁止する、読み取り専用のプロパティを `readonly`  を使用して宣言できます。

以下の例では `age` を読み取り専用として定義しているため、`name` への再代入は成功していますが、 `age` への再代入は型エラーが発生しています。 

```typescript
interface Person {
    name: string,
    readonly age: number
}

let taro: Person = {
    name: "Taro",
    age: 25
}

// OK
taro.name = "Haewon"
//NG: Cannot assign to 'age' because it is a read-only property.
taro.age = 21;
```

#### :memo: 関数型を格納したインターフェース
インターフェースのメンバーに関数を指定する方法についてご紹介します。

- メソッド式
    - `member(): void` 
    - メンバーがオブジェクトのメンバーとして呼び出されることを意図
    - `this`の参照先はオブジェクトとなる
- プロパティ式
    - `member: () => void`
    - メンバーがオブジェクトから独立した関数（アロー関数）として呼び出されることを意図
    - `this`の参照先はレキシカルスコープから決定される

以下のコードは上記を確認するためのサンプルコードです。

```typescript
let value = "Global"

interface Buz {
    value: string,
    memberFn: () => void,
    methodFn(): void
}

let buz: Buz = {
    value: "Buz",
    memberFn: () => console.log(this.value), // NG: The containing arrow function captures the global value of 'this'.
    methodFn() { console.log(this.value) }
}
```

#### :memo: 呼び出しシグネチャ
インターフェースは、関数として呼び出せる値に対して、呼び出し方法を型システムで定義することができます。

これを **呼び出しシグネチャ** といいます。

呼び出しシグネチャは関数型と似ていますが、「`=>`」の代わりに「`:`」を利用します。

```typescript
interface sampleFn {
    (input: string ): void 
}

const hoge: sampleFn = (text: string) => console.log(text);
```
上記の例では `string`型の引数を受け取り値を返さない関数をインターフェースを利用して、定義しています。

##### ユーザー定義のプロパティを持つ関数の作成
呼び出しシグネチャを利用することでユーザー定義のプロパティを持つ関数を定義することができます。

JavaScriptのおさらいでもありますが、**関数もオブジェクト**です。
関数として**独自のプロパティ**を持つことができます。

以下の例では `count`プロパティを持つ関数のインターフェースを定義しています。

```typescript
interface hasPropertyFunc {
    count: number,
    (text: string): void
}

const sampleFn: hasPropertyFunc =  (input: string) => {
    sampleFn.count += 1;

    console.log(input, sampleFn.count)
}

// インタフェースは初期値を代入することができないので初期化。
sampleFn.count = 0;

// OK:  "inputText",  1 
sampleFn("inputText"); 
```

関数内で`count`プロパティを参照しない関数を代入すると、型エラーが発生します。
```typescript
interface hasPropertyFunc {
    count: number,
    (text: string): void
}

// NG: Property 'count' is missing in type '(input: string) => void' but required in type 'hasPropertyFunc'.
const fooFn: hasPropertyFunc =  (input: string) => {
    console.log(input)
}
```

#### :memo: ネストされたインターフェース
オブジェクト型でインターフェースがネストできたことと同様にインターフェース型でもインターフェースの定義をネストさせることができます。

詳細がわからない方はオブジェクト型の記事をご確認ください。

https://qiita.com/PenPe/items/440683cc6e5e4ac0ca32#%E3%83%8D%E3%82%B9%E3%83%88%E3%81%95%E3%82%8C%E3%81%9F%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E5%9E%8B%E3%81%AE%E5%A0%B4%E5%90%88

```typescript
interface Letter {
    author: Author,
    to: string
}

interface Author {
    address: string,
    name: string
}

let letter: Letter = {
    author: {
        name: "Taro",
        address: "Tokyo"
    },
    to: "Hoge"
}
```

#### :memo: インターフェースの拡張
インターフェースは `extends`キーワードを利用することで拡張することができます。

以下の例では`WriteAndRead`（派生インターフェース）に対して、`Write`, `Read`（ベースインターフェース）の機能を追加しています。

```typescript
interface Write {
    writierSkill: number
}

interface Read {
    readerSkill: number
}

interface WriteAndRead extends Write, Read {
    comprehensiveSkill: number
}
```
上記、`WriteAndRead`型を実装した場合、以下3つのプロパティ定義が必要です。
- `writierSkill`
- `readerSkill`
- `comprehensiveSkill`

```typescript
let hoge: WriteAndRead = {
    writierSkill: 10,
    readerSkill: 8,
    comprehensiveSkill: 9
}

// NG: Property 'comprehensiveSkill' is missing in type '{ writierSkill: number; readerSkill: number; }' but required in type 'WriteAndRead'.
let foo: WriteAndRead = {
    writierSkill: 10,
    readerSkill: 8,
}
```

#### :memo: オーバーライド
他のオブジェクト指向言語を学んだ方は馴染みがあると思いますが、
`WriteAndRead`（派生インターフェース）は、`Write`, `Read`（ベースインターフェース）の機能を上書きすることができます（**オーバーライド**）

オーバーライドの制限として、**オーバーライドした型(リテラル)がベースクラスに割当可能**である必要があります

```typescript
interface Page {
  path: string;
}
 
interface IndexPage extends Page {
  path: "/";
}
```

#### :memo: インターフェースのマージ
インターフェースの重要な特徴の1として、マージすることができることです。

マージは拡張（`extends`）とは異なり、同スコープで同名のインターフェースが定義された場合に、それらが結合され、宣言された全てのフィールドを持った1つのインターフェースになることを意味します。

```typescript
interface Merged {
    first: string,
}

interface Merged {
    second: string
}
```

上記の例では`Merged`インターフェースはマージされ、以下2つのプロパティを持つインターフェースと**同意義**なります。

```typescript
interface Merged {
    first: string,
    second: string
}
```

実際に使うタイミングをイメージできない方がいると思いますが、利用例としては以下の通りです。
- 組み込みオブジェクトの拡張（`window`...）
- 外部パッケージに含まれるオブジェクトの拡張

ひとまず、以下を覚えておけばよさそうですね...
> 同スコープで同名のインターフェースが定義された場合に、それらが結合され、宣言された全てのフィールドを持った1つのインターフェースになる

---

### まとめ
以上です！
お疲れ様でした！

プログラム初学者の方は、クラスやインターフェースで詰まることが多いと思います...
これらの領域は、言語の本質となる話なので理解できるまで繰り返すことをお勧めします！


次の記事ではクラスについてまとめていきたいと思います。