【TS】ジェネリクスについて

## ジェネリクスとは
型を引数として受け取ることができる仕組み。

## 基本
```ts
function copy<T>(value: T) {
  return value;
}
const hello = copy<string>("Hello");
console.log(hello);


// 型推論してくれる
function copy<T>(value: T) {
  return value;
}

// <T>を省略すると、以下と同じ
// const Apple = copy<{ name: "apple"; count: 5 }>({ name: "apple", count: 5 });
const Apple = copy({ name: "apple", count: 5 });
console.log(Apple);


// ジェネリクスを複数指定した関数を呼び出す際に、
// ジェネリクスを1つしか呼び出さなくてもエラーにならない
// 2つ目のUは"unknown"になる
function copy<T, U>(value: T) {
  return value;
}
const Apple = copy("apple");

```

## Union型とジェネリクスの違い
- Union型だと自由度が高すぎる
```ts:Union型の場合
function addArr(arr: string[] | number[], item: string | number) {
  const newArr = [...arr];
  newArr.push(item);
  return newArr;
}

// 全要素がstring型なのに、(string | number)[]型
const foo = addArr(["apple", "banana"], "peach");

// (string | number)[]型だから文字列と数字が混在できてしまう
const boo = addArr(["apple", "banana"], 1);
```

```ts:ジェネリクスの場合
function addArr<T>(arr: T[], item: T) {
  const newArr = [...arr];
  newArr.push(item);
  return newArr;
}

// const foo: string[]
const foo = addArr(["apple", "banana"], "peach");

// エラー：numberはstring[]に代入できない
const boo = addArr(["apple", "banana"], 1);
```

## ジェネリクスに制約をつける
```ts
// Tに渡せる型は"{name:string}"を持った型のみ
function copy<T extends { name: string }>(value: T) {
  return value;
}

// OK
const foo = copy({ name: "apple" });


// これもOK
const foo2 = copy({ name: "apple", count: 5 });


// これはNG
const boo = copy({ count: 5 });
```

### extendsをつけたので関数の中で型のプロパティを使える
```ts
function copy<T extends { name: string }>(value: T) {
  
  // valueには必ずstring型のnameプロパティがある
  const _name = value.name;
  return value;
}
const foo = copy({ name: "apple" });

```

## ジェネリクスとkeyofの組み合わせ
```ts:第2引数に指定する型を第1引数のオブジェクトのユニオン型に限定する

function copy<T extends { name: string }, U extends keyof T>(value: T, k: U) {
  const count = value[k];
  return count;
}

// 第2引数に指定する値は、第1引数のプロパティ名に限定する
// OK
const foo = copy({ name: "apple", count: 15 }, "count");

// NG："aaa"は第1引数のオブジェクトのプロパティ名に存在しない
const boo = copy({ name: "apple", count: 15 }, "aaa");

```


## ジェネリクスをclassで使う
```ts
class DataBase<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() {
    console.log(this.data);
  }
}

const stringDatabase = new DataBase<string>();
stringDatabase.add("apple");
stringDatabase.add("banana");
stringDatabase.add("orange");

// [ 'apple', 'banana', 'orange' ]
stringDatabase.get();

stringDatabase.remove("orange");

// [ 'apple', 'banana' ]
stringDatabase.get();

```



## ジェネリクスとPromise
```ts:型を指定しないとPromise<unkwonw>となる
// const promise: Promise<unknown>
const promise = new Promise(resolve => {})
```

```ts:型指定

// const promise: Promise<string>
const promise = new Promise<string>((resolve) => {
  resolve("Hello");
});

// HELLO
promise.then((data) => {
  console.log(data.toUpperCase());
});

```

## ジェネリクスにデフォルトの型を指定
```ts
interface ResponseData<T = string> {
  data: T;
  status: string;
}

// ジェネリクス未指定でstring型となる
const res: ResponseData = {
  data: "APPLE",
  status: "OK",
};

```
```ts:extendsで制約をつけつつデフォルトの型も指定可能
interface ResponseData<
  T extends { msg: string } = { msg: string; code: number }
> {
  data: T;
  status: string;
}

const res: ResponseData<{ msg: string; header: string }> = {
  data: { msg: "完了した", header: "cors" },
  status: "OK",
};
```