【TypeScript】Generics(ジェネリクス)について深く学んでみた

今回は、Genericsについてです。

# Genericsとは？

https://www.typescriptlang.org/docs/handbook/2/generics.html

```typescript
// こんなやつ
type Box<T extends Sweets>
```

Genericsは、**型の決定を遅らせる**ことができます。

型を定義する時には、`<T>` のようにして、「T型」をエイリアスとして、**使うときに決定** できます。

```typescript
// 定義をする時
interface Hoge<T> {
  value: T; // この場合、Tで決定された型が valueの型になる
}

// 使用時
const Foo: Hoge<string> = {
  value: 'valueの型はstring'
}
```

この型の決定を遅らせることの意味は非常に大きいです。

汎用性があがります。

# 変数のGenerics
Genericsを利用する型を宣言する場合、型名称に続いて、慣習的に「T / U / K」などの型エイリアス名称が利用されることがありますが、任意の名称でOKです。

```
ちなみに型エイリアスの略称については諸説あるそうですが、
Typeの「T」、Keyの「K」、Unknownの「U」、Elementの「E」
がよく利用されることが多いそうです。
```

使用時には、Genericsの**型指定が必須**です。

## 基本的な付与

```typescript
interface Hoge<T> {
  value: T
}

const hoge: Hoge = { value: 'ふー' }; // 使用時に型を指定していないので、error
const hoge1: Hoge<string> = { valuse: 'valueの型はstring' };
const hoge1: Hoge<number> = { valuse: 12345 };
```

## Genericsの初期値
Genericsの型は、初期型を指定できます。

この場合、使用時に**型の省略が可能**です。

```typescript
interface Hoge<T = string> {
  value: T
}

const hoge: Hoge = { value: '初期値がstringだよ' };
const hoge1: Hoge<string> = { value: '同じくstringだよ' };
const hoge1: Hoge<number> = { value: 'errorだよ' }; // number型なのでerror
```

## extendsによる制約
Genericsには、制約をつけることができます。

`<T>` だけど**なんでもかんでもだめだよ**という時に制約をつけます。

```typescript
// Tを string か number に限定
interface Hoge<T extends string | number> {
  value: T
}

const hoge: Hoge<string> = { value: 'ストリングです' };
const hoge: Hoge<number> = { value: 12345 };
const hoge: Hoge<boolean> = { value: false }; // string | number ではないので error
```

# 関数のGenerics
関数も同じく、関数宣言時にGenericsを使うことができます。

## 基本的な付与
```typescript
function getHoge<T>(args: T) {
  return { value: args }
}

// アロー関数
const getHoge = <T> (args: T) => {
  return { value: args }
}

getHoge<string>('string')
```

## 関数のGenericsは型の指定が必須ではない
変数の場合は、利用時に型を指定しなければエラーとなりましたが、関数の場合は、利用時の型指定は必須ではなりません。

以下のようにしても推論結果を得ることができます。

```typescript
const hoge = getHoge('string'); // { value: 'string' };
const hoge1 = getHoge(0); // { value: 0 };
const hoge2 = getHoge(false); // { value: false };
const hoge3 = getHoge(null); // { value: null };
```

## アサーションによる明示的な型の付与
Nullable型などを直接適用したい場合、宣言時にアサーションを付与します。

```typescript
const hoge = getHoge(false as boolean | null) // { value: boolean | null }
const hoge1 = getHoge<string | null>(null) // { value: string | null }
``` 

## extendsによる制約
変数と同様に、extendsで限定的な型にできます。

```typescript
function getHoge<T extends string>(args: T) {
  return { value: args }
};

const hoge = getHoge(0); // この時点で「型が違うよー」と教えてくれる
const hoge1 = getHoge('string');
```

## 便利なケース
関数の引数を型安全にできます。

```typescript
interface Args {
  amount: number;
};

function getHoge<T extends Args>(args: Args) {
  return { value: args.amount.toFixed(1) }
};

const hoge = getHoge({ amount: 0 });
const hoge1 = getHoge({ value: 0 }); // Args型を満たしていないため error
const hoge2 = getHoge({ amount: 'string' }); // amount が number ではないため error
```

# Genericsは複数OK
関数の引数と同じく、<T, K, U> のようにGenericsも複数指定することができます。

## keyofによるLookup
今回のケースは、**第1引数がT**、 **第2引数がK(制限付き)** とします。

そしてこのケースでは、第2引数のKはどんな制限かというと、**T** を制限とします。

```typescript
function pick<T, K extends keyof T>(args: T, key: K) {
  return args[key];
};

const obj = {
  name: 'Tom',
  amount: 0,
  flag: false,
};

const value = pick(obj, 'name') // const value: string
const value1 = pick(obj, 'amount') // const value1: number
const value2 = pick(obj, 'flag') // const value2: boolean
const value3 = pick(obj, 'test') // error 'test' プロパティはない
```

# クラスのGenerics
クラス宣言にGenericsを利用すると、**コンストラクターの引数を制約できます** 。

以下の例は、インスタンス生成時にstring型の引数を求めています。

```typescript
class Person<T extends string> {
  name: T
  constructor (name: T) {
    this.name = name;
  };
};

const person = new Person('Tom');
const personName = person.name // const personName: 'Tom'
```

## オブジェクトを引数にとる場合
オブジェクトを引数に取る場合、コンストラクターは「**T extends PersonProps**」という制約を設けて、クラスメンバーに「Indexed Access Types」を利用した型の付与が可能です。

```typescript
interface PersonProps {
  name: string
  age: number
  gender: 'male' | 'female' | 'other'
};

class Person<T extends PersonProps> {
  name: T['name']
  age: T['age']
  gender: T['gender']

  constructor (props: T) {
    this.name = props.name
    this.name = props.age
    this.name = props.gender
  }
}

const person = new Person({
  name: 'Tom'
  age: 20
  gender: 'male'
})
```

# 参考文献
今回参考にさせて頂いたのは以下の書籍です。

https://www.amazon.co.jp/%E5%AE%9F%E8%B7%B5TypeScript-BFF%E3%81%A8Next-js-Nuxt-js%E3%81%AE%E5%9E%8B%E5%AE%9A%E7%BE%A9-%E5%90%89%E4%BA%95-%E5%81%A5%E6%96%87/dp/483996937X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=1643126263&sr=8-1

今回のGenerics以外もとてもわかりやすく学びが多い書籍でした。

以上となります。
ご拝読有難うござました。