【書いて覚えるTypeScript 】インターフェース(interface)編

# 動作環境
今回もPlaygroundでガシガシ書いていきます。
[TypeScript Playground](https://www.typescriptlang.org/play/index.html)


# 参考
[TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/interfaces.html)

# インターフェースとは？
クラスやオブジェクトの構造を定義し、名前をつけれるものです。

# とりあえずインターフェースを使ってみましょう
以下の関数があるとします。

```typescript
function printLabel(labeledObj: { label: string }) {
    console.log(labeledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
```

`{ label: string型の値 }` という形式のオブジェクトを引数として受け取り、キー`label`に対応する値を`console.log`で出力するという関数です。

これを`interface`を使って書き換えていきます。

```typescript
interface LabeledValue {  // 1. interface定義
    label: string;
}

// 2.関数定義
function printLabel(labeledObj: LabeledValue): void {
    console.log(labeledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
```

1. まず、`interface`を使ってキー名が`label`で、値がString型であるという構造に名前をつけます。今回は、`LabeledValue`という名前で定義しています。
2. 関数定義では、先程定義したインターフェース（LabeledValue）を使います。

もし、渡された引数が`LabeledValue`の構造にマッチしていなければエラーが発生します。

```typescript
let erroObj = { name: 'TypeScript' };
printLabel(erroObj);
// => Argument of type '{ name: string; }' is not assignable to parameter of type 'LabeledValue'.
// Property 'label' is missing in type '{ name: string; }' but required in type 'LabeledValue'.
```

# ケースごとのインタフェース利用例
## オプショナルなプロパティがある場合

必ず存在するわけではないプロパティを定義したい場合もあるでしょう。
そのようなときには、関数定義のオプショナル引数と同じように`?`を使います。

例えば、ユーザのプロフィールとして、ブログ、Twitter、Facebook、InstagramのURLがプロパティとしてあるが、
それぞれは必ず存在するわけではないとします。
このような場合は以下のようなインタフェースとなります。

```typescript
interface Profile {
    nickname: string;
    age: number;
    blog?: string;
    twitter?: string;
    facebook?: string;
    instagram?: string;
}
```

## Readonlyなプロパティ
オブジェクト生成時のみ値をセットし、変更できないようにしたい場合は、プロパティ名の前に`readonly`をつけます。

```typescript
interface Profile {
    nickname: string;
    readonly age: number;
}

let profile: Profile = { nickname: 'tom', age: 20 };
profile.age = 22;
// Cannot assign to 'age' because it is a read-only property.
```

また、TypeScriptには、Array型から要素を変更するメソッドをすべて除去した`ReadonlyArray<T>`型もあります。
そのため、`ReadonlyArray<T>`型の配列は要素を変更しようとするとエラーになります。
試してみましょう。

```typescript
let numbers: ReadonlyArray<number> = [1, 20, 3];
numbers[1] = 2; // Index signature in type 'readonly number[]' only permits reading.
numbers.push(4); // Property 'push' does not exist on type 'readonly number[]'.
```

## 未定義のプロパティも許可する方法

定義されたプロパティ以外が存在するとエラーになってしまいます。

```typescript
interface Profile {
    nickname: string;
    readonly age: number;
}

let profile: Profile = { nickname: 'tom', age: 20, gender: 'male' }; // エラー
// Type '{ nickname: string; age: number; gender: string; }' is not assignable to type 'Profile'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Profile'.
```

例えば、`nickname`と`age`さえあれば、あとは何があっても良いという場合であれば、index signatureが使えます。

```typescript
interface Profile {
    nickname: string;
    readonly age: number;
    [key: string]: any;
}

let profile: Profile = { nickname: 'tom', age: 20, gender: 'male' };
```

こうすることで、Profileは「`nickname`と`age`プロパティがあって、それ以外はどんなプロパティがあってもよい」とすることができます。

## インターフェースの継承
インターフェースを継承して、新たにインターフェースを宣言することも可能です。
継承するときは、`extends`を使います。

```typescript
interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = {} as Square;
square.sideLength = 10;
square.color = 'red';
```

また、カンマ区切りで複数のインターフェースを継承することもできます。

```typescript
interface Square extends Shape, Rectangle {
    ...
}
```

## 関数型
インターフェースは関数型も表すことができます。

引数とその型、そして戻り値の型を記述します。

```typescript
interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    let result = source.search(subString);
    return result > -1;
}
```

## インターフェースとクラス

インターフェースは`implements`キーワードを使って、クラスに特定の構造を強制することもできます。
つまり、インターフェースで宣言したプロパティや関数をクラスが持っていないといけないという制約をつけることができます。
（JavaやC#では最もよく使われる方法らしいです。）

例えば、デジタル時計クラス(`DigitalClock`)とアナログ時計クラス（`AnalogClock`）があるとして、どちらのクラスにも`currentTime`プロパティと`setTime`メソッドを実装していてほしい場合、インターフェースをつかうと以下のように書くことができます。

```typescript
interface ClockInterface {
    currentTime: Date;
    setTime(date: Date): void;
}

class DigitalClock implements ClockInterface {
    currentTime: Date;
    constructor() {
        this.currentTime = new Date();
    }

    setTime(date: Date) {
        this.currentTime = date;
    }
}

class AnalogClock implements ClockInterface {
    currentTime: Date;
    constructor() {
        this.currentTime = new Date();
    }

    setTime(date: Date) {
        this.currentTime = date;
    }
}
```

もし、`ClockInterface`を満たさない構造だったら以下のようにエラーになります。

```typescript
class AnalogClock implements ClockInterface {
    constructor() {
    }

    setTime(date: Date) {
    }
}
// Class 'AnalogClock' incorrectly implements interface 'ClockInterface'.
// Property 'currentTime' is missing in type 'AnalogClock' but required in type 
// 'ClockInterface'.
```


これだったら継承でも良くない？と思ってしまいます。
「基底クラスに抽象メソッドを定義して、子クラスでその抽象メソッドをオーバーライドいないといけない」といったように実装すれば同じことができます。

もちろん継承でも良いのですが、TypeScriptは単一継承なので、複数のクラスを継承することができません。しかし、インターフェースの場合は、いくつでも`implements`できるのです。

そのため、目的ごとにいくつかのインターフェースを宣言して、そのうち必要なものを`implements`するといったことができます。

```typescript
class Watch implements ClockInterface, WatchInterface {
  // ...
}
```


以上、Interfaceについてでした。
次回は、[TypeScript 書いて覚えるジェネリック](https://qiita.com/nomuyoshi/items/b1f83953d297208e901f)