【書いて覚えるTypeScript 】ジェネリクス(Generics)編

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


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

# ジェネリクスとは
汎用的なメソッドやクラスに対して、様々な型を紐付けることができる機能です。

通常TypeScriptでは以下のように関数を定義します。

```typescript
function returnValue(value: string): string {
  return value;
}
```

ですが、これだと引数にString型しか渡せません。Number型やArray型など他の型でも使いたいとなったら、同じような関数を何個も定義することになってしまいます。

**ここで、ジェネリクスの出番です。先程の`returnValue`をジェネリクスを使って書き換えてみます。**

```typescript
function returnValue<T>(value: T): T {
  return value;
}

let result = returnValue<number>(1);
console.log(result); // => 1

result = returnValue<string>('Hello, Generic!!');
console.log(result); // => Hello, Generic!!
```

関数名の後`<T>`を追加が追加されています。この`T`は型引数と言いい、型を引数のように呼び出し時に渡すことができます。

今回の例だと、型引数が引数`value`と戻り値のデータ型にもなっているので、
引数として受け取った型`T`のvalueを受け取り、戻り値の型も`T`になるという意味です。

呼び出すときには、`returnValue<string>('Hello, Generic!!');`といったように、`<>`でデータ型を囲って指定します。

※ 慣例的に、型引数には`T`, `U`, `R`などがよく使われるようです。

# ジェネリクスと関数

もう少し複雑な例をあげようと思います。
以下のような、`id`プロパティを持つ`Post`クラスと`Category`クラスがあるとします。

```typescript
class Post {
    id: number;
    title: string;
    constructor(id: number, title: string) {
        this.id = id;
        this.title = title;
    }
}

class Category {
    id: number;
    name: string;
    constructor(id: number, name: string) {
        this.id = id;
        this.name = name;
    }
}
```

この２つのクラスの配列に対して同一の`id`のものがあれば、要素を置き換えるような`replace`関数を実装してみます。

```typescript
replace(array, newItem);
```

（Reduxのreducerで良く行いそうな処理ですね。）

## ひとまず、実装してみる

```typescript

function replace<T>(data: T[], newItem: T): T[] {
    const index = data.findIndex(item => item.id === newItem.id); // Property 'id' does not exist on type 'T'.
    if (index < 0) {
        console.error(`id: ${newItem.id} not found.`); // Property 'id' does not exist on type 'T'.
        return data;
    }
    
    return [
      ...data.slice(0, index),
      newItem,
      ...data.slice(index + 1),
    ];
}
```

完成！！簡単！と言いたいところですが、エラーが出てしまっています。

`Property 'id' does not exist on type 'T'.` (Tには、`id`プロパティが無いよー)

そこで、「`T`には`id`プロパティがある」という制約を付けます。

## 型引数`T`に制約をつける
`extends`キーワードを使うことで制約をつけれます。
今回の例では、`<T>`を`<T extends { id: number }`とするれば、「`T`は `{ id: number}` とい構造を持っている（継承している）」という制約を付けることができます。

その他にも、例えば、`<T extends MyClass>` とクラス名を書くことで、型引数`T`には、「`MyClass`とその派生クラスだけを受け取る」という制約をつけることができます。

それでは、先程のコードを書き換えましょう。

```typescript
function replace<T extends { id: number }>(data: T[], newItem: T): T[] {
    const index = data.findIndex(item => item.id === newItem.id);
    if (index < 0) {
        console.error(`id: ${newItem.id} not found.`);
        return data;
    }
    
    return [
      ...data.slice(0, index),
      newItem,
      ...data.slice(index + 1),
    ];
}
```

これで完成です。実際に呼び出して確認してみます。

## 動作確認


```typescript
const postArray = [
    new Post(1, 'post1'),
    new Post(2, 'post2'),
    new Post(3, 'post3'),
    new Post(4, 'post4')
];

const categoryArray = [
    new Category(1, 'typescript'),
    new Category(2, 'coffeescript'),
    new Category(3, 'es6'),
]

const newPost = new Post(3, 'TypeScriptについて');
const newCategory = new Category(1, 'TypeScript');

let postArrayResult = replace(postArray, newPost);
console.log(postArrayResult);

let categoryArrayResult = replace(categoryArray, newCategory);
console.log(categoryArrayResult);
```

<img width="332" alt="スクリーンショット 2019-10-06 23.23.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104274/fa81531f-07d6-1f47-1586-9827c8ca6e01.png">

# ジェネリクスとクラス

クラス名の後に`<T>`のような型引数を付与すれば、クラスに対しても使用することができます。

以下は、[TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/generics.html)の例に少し手を加えたものです。

```typescript
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
    getZeroValue(): T {
        return this.zeroValue;
    }
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) { return x + y; };
console.log(myGenericNumber.getZeroValue()); // => 0


let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "test")); // => test
```

クラスに付与された型引数は、そのクラスのプロパティやメソッド定義で使用することができます。


# ジェネリクスまとめ

- 関数名やクラス名の後に`<T>`といったように型引数を付与する
- 型引数は関数内やクラス内で使用することができる
- 型引数に制約を付けたい場合は`<T extends ParentClass>`のように`extends`を使う