【TypeScript】ジェネリクスを学ぶ

# Generics(ジェネリクス)とは
まず初めにジェネリクスはTypeScriptだけに限った機能ではありません。
C#やJavaにも存在する機能で型を抽象化して定義することができます。

つまり、あらかじめ特定の型に定義せず、曖昧な状態のままで定義することができます。
そして利用時点で型指定を行い、特定の型に確定します。

このように利用されるまでは型定義が曖昧で様々な型で利用されることから_「総称型」_とも呼ばれます。

それではサンプルコードとともに学習していきます。

# サンプルコード
## メソッド

下記のコードに定義するfetcherは単純にエンドポイントを指定して、取得したデータをjson形式に変換してから返却するfetchAPIのラップ関数です。
getUserはfetcherを利用して取得したユーザー情報を利用元に返却します。

:::note warn
コードを単純化させるため、あえてエラーハンドリングやパラメータ及びオプションの付与は行っていません。
:::

```TypeScript
const fetcher = async(endPoint: string): Promise<any> => {
    const res = await fetch(endPoint).then(res => res.json());
    return res;
}

export const getUser = async(): Promise<any>  => {
    const user = await fetcher('https://example.com/api/user');
    return user;
}
```

上記のコードのままでは返却値の型は`Promise<any>`という型になります。
せっかくTypeScriptで型定義の恩恵を受けているのに、any型で返却されるのは望ましくありません。

そこでジェネリクスを用いて、上記のコードを修正します。

```TypeScript
const fetcher = async<T>(endPoint: string): Promise<T> => {
    const res = await fetch(endPoint).then<T>(res => res.json());
    return res;
}

/**
functionを用いる場合のジェネリクスの定義位置
export async function fetcher<T>(endpoint: string): Promise<T> {
    const res = await fetch(endpoint).then<T>(res => res.json())
    return res
}
**/

export const getUser = async(): Promise<{id: number, name: string}> => {
    const user = await fetcher<{id: number, name: string}>('https://example.com/api/user');
    return user;
}
```

このようにジェネリクスに置き換えることでany型を返却しなくても良くなりました。
また、ジェネリクスを用いることでgetUserだけに依存することなく、その他のデータ取得関数からも利用可能な汎用的なfetchAPIのラップ関数として定義することができました。

また、ジェネリクスは返り値だけでなく、引数にも利用可能です。
以降はfetchAPIなどの具体的な例がわかりづらい方のためにもっと単純な例を挙げます。

```TypeScript
// 引数として与えた値がそのまま返却される関数
const functionA = <T>(param: T): <T> => {
    return param;
}

const returnValue1 = functionA(1);
const returnValue2 = functionA('文字列');

console.log(typeof returnValue1); // number
console.log(typeof returnValue2); // string
```

このように返り値と同様に引数にもジェネリクスを適用させることができます。

## 複数の引数を持つ関数
複数の引数を持つ関数でもそれぞれの引数に個別のジェネクスを定義することも可能です。

```TypeScript
// 引数のboolがtrueの場合はparam1を返却し、falseの場合はparam2を返却する関数
const functionA = <T, U>(bool: boolean, param1: T, param2: U): T | U => {
    if(bool) return param1;
    return param2;
}

const returnValue1 = functionA(true, 1, '文字列');
const returnValue2 = functionA(false, 1, '文字列');

console.log(typeof returnValue1) // number
console.log(typeof returnValue2) // string
```

ちなみにTとかUとかは慣習的に良く利用されているだけで、例えば`Element`などの文字やその他のアルファベットでも定義することができます。

## クラス
前項までの関数と同様にクラスにもジェネリクスを利用することができます。

```TypeScript
class SomeClass<T> {
    value: T;

    constructor(value: T) {
        this.value = value;
    }

    getValue(): T {
        return this.value;
    }
}

const obj = new SomeClass('文字列');
console.log(typeof obj.getValue()); // string
```

## インターフェース
関数やクラスと同様にインターフェースにもジェネリクスを利用できます。

```TypeScript
interface SomeInterface<T, U> {
    value1: T;
    value2: U;
}

const obj: SomeInterface<string> = { value1: '文字列', value2: 1 };
console.log(typeof obj.value1); // string
console.log(typeof obj.value2); // number
```

## インターフェースを継承したジェネリクス
通常のジェネリクスはどのような型であっても受け入れることができます。
ただし、受け入れる値の一部を事前に定義しておきたい場合もあります。

```TypeScript
const getId = <T>(param: T): number => {
    return param.id; // プロパティ 'id' は型 'T' に存在しません。ts(2339)
}
```

上記のコード例であるgetIdの引数Tはどのような値でも受け入れるので、idプロパティを持つオブジェクトが引数として渡されるのかはわかりません。
そこで役立つのがジェネリクスは型を継承できるということです。

```TypeScript
interface User {
    id: number;
    name: string;
}

const getId = <T extends User>(param: T): number => {
    return param.id;
}

const id = getId({id: 1, name: 'テスト太郎'});

console.log(typeof id); // number
```

このように記述することでUserインターフェースのプロパティを持っているジェネリクスを定義することができます。
要するにgetIdを実行する場合はid及びnameを必ず持つ引数を渡さなければならなくなり、引数がidを持つことが確定します。

ちなみにインターフェースを継承しただけなので、継承したプロパティを持ってさえいれば、その他のプロパティを持っていたとしても引数として受け入れられます。

```TypeScript
interface User {
    id: number;
    name: string;
}

const getId = <T extends User>(param: T): number => {
    return param.id;
}

// telを渡してもエラーにはならない
const id = getId({id: 1, name: 'テスト太郎', tel: '090-0000-0000'};
```

## 参考サイト
[Documentation - Generics - TypeScript](https://www.typescriptlang.org/docs/handbook/2/generics.html)
[ジェネリック型 - TypeScript Deep Dive 日本語版](https://typescript-jp.gitbook.io/deep-dive/type-system/generics)
[参ノ型：ジェネリクス舞い - Zenn](https://zenn.dev/tkdn/books/type-breathing/viewer/3-generics)