【TypeScript】ジェネリクスの仕組み 🤔

# ジェネリクスとは

型を引数として受け取ることができる仕組み。
型を引数に取れることで、型だけが異なる同じ関数の複製などを避けられる👏🏻

> ジェネリクスを用いると、型の安全性とコードの共通化を両立することができます。

[サバイバルTypeScript：ジェネリクス](https://typescriptbook.jp/reference/generics)

下記の例では、取れる引数の値を`number型`にしている。
しかし、型を指定したままでは`string`やオブジェクトなど他の型を取りたい場合に利用できず、型が異なるだけの関数を作成しなければならない。
```ts
function copy(value: number): number {
  return value;
}
console.log(copy(11));
```
ジェネリクスを利用し、型を引数に取ることで、再利用性を高めることができる。
下記の例のように`<T>`と記述することで、そこに型の実引数が渡ってくる。
`<T>`部分は自由に記載できるが、慣習的にはTypeという意味の`<T>`にするよう。
```ts
function copy<T>(value: T): T {
  return value;
}
console.log(copy<string>("hello"));
```

複数取ることもできる
```ts
function copy<T, T1, T2>(value: T): T {
  return value;
}
```
`copy<string>("hello")`の型の実引数部分を省略できる。
省略しても、関数の実引数から型推論される。
下記の例では型の実引数を省略しているが、`.`で繋ぐと、引数で渡している`hello`から型推論され、stringが持つメソッドが補完で出てきていることがわかる。
![スクリーンショット 2022-12-03 14.50.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/a6348cc5-ba7c-d98f-87b7-8094dcced2ad.png)

## 型パラメータ(引数)に制約をつける方法

`extends`キーワードを使うと制約をつけられる。
下記の例では、`{ name: string }`とextendsしている。

```ts
function copy<T extends { name: string }>(value: T): T {
  return value;
}
console.log(copy({ name: "Quill" }));
```
プロパティ名を`extends`の内容と異なるものにするとエラー
![スクリーンショット 2022-12-03 18.00.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/45c3856e-553b-5789-d0e1-df75060abff8.png)

## keyof演算子

`keyof オブジェクトの型`とすることで、オブジェクトの型のキーを取り出して、返す。

> keyofはオブジェクト型からプロパティ名を型として返す型演算子です。

[サバイバルTypeScript：keyof演算子](https://typescriptbook.jp/reference/type-reuse/keyof-type-operator)

```ts
type K = keyof { name: string; age: number };
```
`type K`はnameとageのユニオン型になっていることがわかる。
![スクリーンショット 2022-12-04 18.18.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/b699b333-d19a-f31f-c8f1-0252430b6bed.png)

## Classに対してジェネリクスを使用する方法

classに対してもジェネリクスは使用できる。
下記は使用例。

```ts
class LightDatabase<T extends string | number | boolean> {
  private data: T[] = [];
  add(item: T) {
    this.data.push(item);
  }
  remove(item: T) {
    this.data.splice(this.data.indexOf(item), 1);
  }
  get() {
    return this.data;
  }
}
const stringLightDatabase = new LightDatabase<string>();
stringLightDatabase.add("Apple");
stringLightDatabase.add("Banana");
stringLightDatabase.add("Grape");
stringLightDatabase.remove("Banana");
console.log(stringLightDatabase.get()); // → [ 'Apple', 'Grape' ]
```

## interfaceに対してジェネリクスを使用する方法

```ts
interface TmpDatabase<T> {
  id: number;
  data: T[];
}
const tmpDatabese: TmpDatabase<number> = {
  id: 3,
  data: [32],
};
```

## ユーティリティ型(utility type)の紹介

ユーティリティ型とは、型のライブラリ。

> ユーティリティ型(utility type)は、型から別の型を導き出してくれる型です。functionが実行時の世界の関数だとしたら、ユーティリティ型は型の世界の関数といったイメージです。

[サバイバルTypeScript：ユーティリティ型](https://typescriptbook.jp/reference/type-reuse/utility-types)

上記サバイバルTypeScriptで代表的なユーティリティ型を掲載してくれているのが、そのうちの一つである`Partial`というユーティリティ型を使用した例。

> Partial<T>は、オブジェクト型Tのすべてのプロパティをオプションプロパティにするユーティリティ型です。

[サバイバルTypeScript：Partial](https://typescriptbook.jp/reference/type-reuse/utility-types/partial)

```ts
interface Todo {
  title: string;
  text: string;
}
type Todoable = Partial<Todo>
```

全てのプロパティがオプションになっていることがわかる。
![スクリーンショット 2022-12-05 22.03.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/f3a35f0e-e8e7-0ba2-6761-3e56c8b9b167.png)

## Mapped Typesを使用する方法

Mapped Typesは型のfor文である。

例1)string型で展開

```ts
type MappedTypes = {
  [P in 'tomato' | 'pumpkin']: string
}
```
![スクリーンショット 2022-12-05 22.19.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/cf598920-64fd-ade3-35ab-b0d924c5103c.png)

例2)文字列リテラルで展開

```ts
type MappedTypes = {
  [P in 'tomato' | 'pumpkin']: P
}
```
![スクリーンショット 2022-12-05 22.18.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/32a093e6-b038-109d-ff51-0e20e4dd6789.png)

先頭に`readonly`をつけると全てのプロパティがreadonlyになる。
```ts
type MappedTypes = {
  readonly [P in 'tomato' | 'pumpkin']: string
}
```
![スクリーンショット 2022-12-05 22.24.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/4f8f092d-95b0-b6e2-4bce-494fcb7a3d28.png)

`?`をつければオプショナルになる。
```ts
type MappedTypes = {
  readonly [P in 'tomato' | 'pumpkin']?: string
}
```
![スクリーンショット 2022-12-05 22.25.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/0ab7e9e5-ac09-d4a9-c63a-ef4212f9f2ab.png)

## Conditional Types

Conditional Typesとは型のif文。
`extends`の左側が右側に代入できるかどうかの条件分。

`tomato`は`string`に代入可能だったら、`number型`、そうでなければ`boolean型`を返す。
```ts
type ConditionalTypes = 'tomato' extends string ?  number : boolean;
```
![スクリーンショット 2022-12-05 22.35.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/2d2c470f-a06c-3b20-5a6a-bb69b6746a93.png)