TypeScript Handbook を読む (3. Interfaces)

[TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/basic-types.html) を読み進めていく第三回目。

1. [Basic Types](http://qiita.com/murank/items/9c1f633545842c953947)
1. [Variable Declarations](http://qiita.com/murank/items/913f1c1e4e725047436c)
1. Interfaces (今ココ)
1. [Classes](http://qiita.com/murank/items/7e8f15225bc8259e9bf3)
1. [Functions](http://qiita.com/murank/items/e310ce68ed5a0058d42d)
1. [Generics](http://qiita.com/murank/items/144b686d34fd433a97a3)
1. [Enums](http://qiita.com/murank/items/cc487562b21bae3728ba)
1. [Type Inference](http://qiita.com/murank/items/e67ad7426020321228e9)
1. [Type Compatibility](http://qiita.com/murank/items/606efb0615294a6826e3)
1. [Advanced Types](http://qiita.com/murank/items/bc1776efbecbd2120d94)
1. [Symbols](http://qiita.com/murank/items/04b3b31caa677750f240)
1. [Iterators and Generators](http://qiita.com/murank/items/7ceec53dde886f31a682)
1. [Modules](http://qiita.com/murank/items/0fc4e059be8ea5e1c274)
1. [Namespaces](http://qiita.com/murank/items/0dfad4a0bb91aaa6c97b)
1. [Namespaces and Modules](http://qiita.com/murank/items/0be1978a3515fc76e7ba)
1. [Module Resolution](http://qiita.com/murank/items/5f11466474d49498bbd5)
1. [Declaration Merging](http://qiita.com/murank/items/ec90ba04edab1ea3718e)
1. [JSX](http://qiita.com/murank/items/90164aa9a3bed3c49a80)
1. [Decorators](https://qiita.com/murank/items/580e34883e0832ca7aed)
1. [Mixins](https://qiita.com/murank/items/e77ad8359ed1fd82822d)
1. [Triple-Slash Directives](https://qiita.com/murank/items/faff57883338ebdeda12)
1. [Type Checking JavaScript Files](https://qiita.com/murank/items/588eaddfe8af59914658)


# Interfaces
[原文](https://www.typescriptlang.org/docs/handbook/interfaces.html)

## Our First Interface
インタフェースの使われ方の一番単純な例は以下の通りです。

コンパイラは `printLabel` メソッドの引数に文字列型の `label` プロパティが含まれていることをチェックします。
一方で、それ以外のプロパティがあったとしてもコンパイラは何もチェックしません。

```ts:TypeScript
function printLabel(labelledObj: { label: string }) {
    console.log(labelledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
```
```js:JavaScript
function printLabel(labelledObj) {
    console.log(labelledObj.label);
}

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

それでは実際にインタフェースを使ってみましょう。

```ts:TypeScript
interface LabelledValue {
    label: string;
}

function printLabel(labelledObj: LabelledValue) {
    console.log(labelledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
```
```js:JavaScript
function printLabel(labelledObj) {
    console.log(labelledObj.label);
}

var myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
```
>インタフェースはチェックにだけ使われて、コンパイルしたら消えるのね

コンパイラはプロパティがどのような順番になっていても気にしないことを覚えておいてください。


## Optional Properties
必ずしもすべてのプロパティが必須とは限らないため、そのようなプロパティを表すにはプロパティ名の末尾に `?` を付けてください。

```ts:TypeScript
interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {
    let newSquare = {color: "white", area: 100};
    if (config.color) {
        newSquare.color = config.color;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

let mySquare = createSquare({color: "black"});
```
```js:JavaScript
function createSquare(config) {
    var newSquare = { color: "white", area: 100 };
    if (config.color) {
        newSquare.color = config.color;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

var mySquare = createSquare({ color: "black" });
```
>任意のプロパティを指定しても JavaScript のコードは特に変わらないか


任意のプロパティの利点は (プロパティ名の打ち間違い等で) インタフェースに定義されていないプロパティを誤って使用しないようチェックできる点です。

```ts:TypeScript
interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
    let newSquare = {color: "white", area: 100};
    if (config.color) {
        // エラー。'clor' プロパティは 'SquareConfig' に存在しない
        newSquare.color = config.clor;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

let mySquare = createSquare({color: "black"});
```

## Readonly properties
読み取り専用にしたいプロパティはプロパティ名の前に `readonly` を付けてください。

```ts:TypeScript
interface Point {
    readonly x: number;
    readonly y: number;
}
```

この場合、オブジェクトリテラルで初期化することはできますが、変更することはできません。

```ts:TypeScript
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // エラー!
```

TypeScript では `ReadonlyArray<T>` クラスを用意しています。
これは `Array<T>` に似ていますが、オブジェクトを変更可能なメソッドが削除されています。

```ts:TypeScript
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // エラー!
ro.push(5); // エラー!
ro.length = 100; // エラー!
a = ro; // エラー!
```

最後の例は `ReadonlyArray` オブジェクトを普通の配列に代入できないことを表していますが、キャストを使用すれば可能です。

```ts:TypeScript
a = ro as number[];
```

### `readonly` vs `const`
`readonly` と `const` の使い分けは簡単です。
対象が変数であれば `const` を、プロパティであれば `readonly` を使ってください。


## Excess Property Checks
インタフェースと任意のプロパティを組み合わせると、次のような落とし穴にハマることがあります。

```ts:TypeScript
interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
    // ...
}

let mySquare = createSquare({ colour: "red", width: 100 }); // おっと、'color' のつもりが 'colour' としてしまった
```

`'color'` プロパティは必須ではないため、このままでは `'width'` プロパティだけが使用されてしまいます。

これを防ぐために TypeScript ではオブジェクトリテラルに対して特別なチェックを行うようにしています。
具体的には、他の変数への代入時やメソッドの引数として渡された時に "対象の型" に含まれないプロパティが存在するとエラーとして扱います。

```ts:TypeScript
// エラー。'colour' は 'SquareConfig' に含まれない
let mySquare = createSquare({ colour: "red", width: 100 });
```

このチェックをバイパスするには単にキャストするだけです。

```ts:TypeScript
// 'SquareConfig' に 'opacity' は含まれないけど問題ない
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
```

もっと良いやり方は余分なプロパティを受け取るために文字列型のインデックスシグネチャを追加することです。

```ts:TypeScript
interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}
```
>`'propName'` と指定しているものの、別に `引数.propName` で余分なプロパティにアクセスできるわけではない点に注意。
>インデックスシグネチャについては[後述](#indexable-types)。

最後に紹介する方法は一度変数に代入することです。

```ts:TypeScript
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);
```

単純なコードではそうは思わないかもしれませんが、コードがより複雑になってくると、これらのテクニックを使う必要が出てくるかもしれません。
ただし、多くの場合では、余分なプロパティの存在はバグであることを意識しておいてください。

## Function Types
インタフェースは特定のプロパティを持つオブジェクトを表す以外に、メソッドの型を表すこともできます。
これには引数と戻り値だけを持つ関数宣言のように表現します。
この時、引数リストには名前と型が必須です。

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

後は他のインタフェースと同じように使用するだけです。

```ts:TypeScript
let mySearch: SearchFunc;

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

この時、引数の名前は必ずしも一致する必要はありません。

```ts:TypeScript
let mySearch: SearchFunc;

mySearch = function(src: string, sub: string): boolean {
    let result = src.search(sub);
    return result > -1;
}
```

型を指定していなくても、TypeScript は変数の型を元に型推論を行います。

```ts:TypeScript
let mySearch: SearchFunc;

mySearch = function(src, sub) {
    let result = src.search(sub);
    return result > -1;
}
```
>とはいえ、なるべく型は指定した方がいいと思う


## Indexable Types
TypeScript では `a[10]` や `ageMap["daniel"]` のように、インデックスでアクセスできる型も表現できます。
インデックス可能な型は、インデックスとして指定可能な型とその戻り値の型を指定した *インデックスシグネチャ* を持ちます。

```ts:TypeScript
interface StringArray {
    [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];
```

インデックスシグネチャは **文字列型** と **数値型** の 2 種類をサポートしています。
両方の型をサポートすることも可能ですが、その場合、数値型のインデックスシグネチャの戻り値の型は、文字列型のインデックスシグネチャの戻り値の型のサブクラスである必要があります。
なぜなら、JavaScript では数値型のインデックスは文字列に変換されてから使用されるためです。

```ts:TypeScript
class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}

// エラー。文字列型でアクセスすると Animal が返ってくることがある。
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Dog;
}
```

それ以外に、インデックスシグネチャはすべてのプロパティの型を統一させる効果もあります。

```ts:TypeScript
interface NumberDictionary {
    [index: string]: number;
    length: number;    // OK。length は数値型
    name: string;      // エラー。name は数値型またはそのサブクラスではない
}
```

最後に、インデックスシグネチャに `readonly` を指定することで、インデックスを指定した代入を禁止することもできます。

```ts:TypeScript
interface ReadonlyStringArray {
    readonly [index: number]: string;
}

let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // エラー!
```

## Class Types
### Implementing an interface
インタフェースのもっとも一般的な用途はクラス宣言に使用することです。

```ts:TypeScript
interface ClockInterface {
    currentTime: Date;
}

class Clock implements ClockInterface {
    currentTime: Date;
    constructor(h: number, m: number) { }
}
```

同様にクラスで定義すべきメソッドを宣言することもできます。

```ts:TypeScript
interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}
```

### Difference between the static and instance sides of classes

クラスとインタフェースを使用するにあたって、クラスは静的メンバとインスタンスメンバという 2 種類のメンバを持つ点を意識しておいてください。

例えば、以下の例はエラーとなります。
なぜなら、インタフェースを実装する際にはインスタンスメンバしかチェックされないため、静的メンバであるコンストラクタがチェックの対象にならないからです。

```ts:TypeScript
interface ClockConstructor {
    new (hour: number, minute: number);
}

class Clock implements ClockConstructor {
    currentTime: Date;
    constructor(h: number, m: number) { }
}
```
>コンストラクタ(コンストラクタシグネチャ)を持つインタフェースは実装(継承)できないということかな？

代わりに静的メンバを直接使用する必要があります。
以下の例ではコンストラクタ用のインタフェース (`'ClockConstructor'`) とメソッド用のインタフェース (`'ClockInterface'`) をそれぞれ用意しています。

```ts:TypeScript
interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick();
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
```


## Extending Interfaces
インタフェースはクラスのように拡張することもできます。

```ts:TypeScript
interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
```

また、複数のインタフェースを基に拡張することも可能です。

```ts:TypeScript
interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
```

## Hybrid Types
インタフェースでは、これまでに挙げた機能を組み合わせて使用することが可能です。

```ts:TypeScript
interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = <Counter>function (start: number) { }; // まだインタフェースをすべて実装していないため、'Counter' 型として宣言していない (できない)
    counter.interval = 123;
    counter.reset = function () { };
    return counter; // 'Counter' 型のすべてのプロパティ/メソッドを用意したため、'Counter' 型として返却できる
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
```

## Interfaces Extending Classes
クラスを拡張してインタフェースを作成した場合、private/protected メンバも含めてクラスのメンバはすべて継承されますが、その実装は引き継がれません。
つまり、private/protected メンバを持つクラスを継承したインタフェースは、そのクラス自身、またはそのサブクラスでしか実装することはできないということです。

このことは特定のプロパティを持つサブクラスに対してのみ、動作するコードを作成したい場合に役立ちます。
この時、サブクラスで必要なことは基底クラスを継承することだけです。

```ts:TypeScript
class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {
    select() { }
}

class TextBox extends Control {
    
}

// エラー: 'state' プロパティが 'Image' クラスに存在しない
class Image implements SelectableControl {
    select() { }
}

class Location {
    
}
```

例えば、上記の例では `SelectableControl` クラスは private な `state` プロパティを含む、`Control` クラスのすべてのメンバを含んでいます。
`state` プロパティは private メンバのため、`SelectableControl` を実装できるのは `Control` の子孫クラスのみとなります。

`Control` クラス内では `SelectableControl` のインスタンスを介して `state` メンバにアクセスすることができます。
そのため、`SelectableControl` は `select` メソッドを持つ `Control` クラスのように振る舞います。
`Button`、`TextBox` クラスは (`'Control'` クラスを継承し、かつ、`select` メソッドを持っているため) `SelectableControl` のサブクラスですが、`Image`、`Location` クラスは違います。
>`TextBox` は `select` メソッドを持たないから、`SelectableControl` ではないような気が...