TypeScriptのGenerics(ジェネリクス)型をまとめる

## はじめに

毎週参加しているTypeScript勉強会で、[公式ドキュメント](https://www.typescriptlang.org/docs/handbook/2/functions.html#generic-functions)を読み解いたのでまとめます。

本記事の対象は、**関数におけるGenerics**のみになります。

## Genericsとは？

例えば、配列の最初の要素を返す関数があるとする。

```ts
function firstElement(arr: any[]) {
  return arr[0];
}
```

上記は動作はするが常に`any`を返してしまう。
こんな時に活躍するのが`Generics`である。

```ts
function firstElement<Type>(arr: Type[]): Type | undefined {
  return arr[0];
}
```

実行時に`<Type>`という型の情報を引数のような形でもらい、引数や返り値の`Type`に割り当てることができる。

```ts
function firstElement<Type>(arr: Type[]): Type | undefined {
  return arr[0];
}

console.log(firstElement<string>(['hoge', 'fuga']))
// output: "hoge"

console.log(typeof firstElement<string>(['hoge', 'fuga']))
// output: "string"

// <string>は省略可能（TypeScriptが推論してくれる）
firstElement(['hoge', 'fuga'])
```

上記の例では、実行時に`<string>`を渡すことで、`firstElement`の引数及び返り値が`string`に制約される。

まとめるとGenericsとは、

- 型の情報を引数のような形`<>`でやり取りできる
- 定義時にはどんな型になるか分からない処理で、実行時に型を制約したい場合に使う
- Genericsを使うことで、処理内の**2つ以上**の場所で使用することにより、例えば関数の引数と返り値のリンクを作成できる

## Generics関数を良い感じに書く方法（ガイドライン）

※全て公式に載っている内容です。

### extendsによる型制約に注意する

また配列の最初の要素を返す関数を例にする

```ts
function firstElement1<Type>(arr: Type[]) {
  return arr[0];
}

// 悪い例
function firstElement2<Type extends any[]>(arr: Type) {
  return arr[0];
}
 
// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);
```

本来`extends`を使うことで`Generics`をより制限できるのだが、上記の例では`extends`を使用していない`firstElement1()`は`number`を返しているのに対し、`extends`を使用している`firstElement2()`は`any`を返しているので、良くない`extends`の使い方である。

これは「TypeScriptは`extends`による制約をしている場合、実行時に型を解決するのではなく、実行前に`extends`による制約を使用して型解決を行なっているから」である。

つまり、`extends`を使用する場合は、型を緩めるような使い方（上記のような`<Type extends any[]>`）はすべきではない。

:::note info
Generics内でextendsを書く場合は、型をより制限するような形で書く
:::

参考：
[extends(良い使い方はこちらを参考に)](https://www.typescriptlang.org/docs/handbook/2/functions.html#constraints)
[Push Type Parameters Down(引用元)](https://www.typescriptlang.org/docs/handbook/2/functions.html#push-type-parameters-down)


### Genericsで定義する型パラメータはできるだけ少なく

```ts
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
  return arr.filter(func);
}
 
function filter2<Type, Func extends (arg: Type) => boolean>(
  arr: Type[],
  func: Func
): Type[] {
  return arr.filter(func);
}
```

`<>`内を見ると、上は`<Type>`のみなのに対し、下は`<Type, Func extends (arg: Type) => boolean>`と記述が多い。
これがどういうことかと言うと、「呼び出し元から無駄なコードが発生する」「可読性が落ちる」「推論の精度が落ちる」などの問題が発生してしまう。

:::note info
Genericsで定義する<>内は、できるだけシンプルに書く
:::

参考：
[Use Fewer Type Parameters(引用元)](https://www.typescriptlang.org/docs/handbook/2/functions.html#use-fewer-type-parameters)

### Genericsで定義する型パラメータは、処理内で2回以上使用する場合のみ使う

```ts
// bad
function greet<Str extends string>(s: Str) {
  console.log("Hello, " + s);
}
 
// good
function greet(s: string) {
  console.log("Hello, " + s);
}
```

Genericsは、複数の値の型を関連づけるものなので、処理内で1回しか登場しない型には使うべきではない。

:::note info
型パラメータが1回しか登場しない場合はGenericsは使用しない
:::

参考：
[Type Parameters Should Appear Twice(引用元)](https://www.typescriptlang.org/docs/handbook/2/functions.html#use-fewer-type-parameters)


## 最後に

公式には、Genericsは「こう書くべき」ではなく「こう書いてはいけない」というアンチパターンが多く紹介されていました。
普通に書いている分には、そう書かないよな..？というものもありましたが、これらのルールをきちんと守ってGenerics書いていきたいです。