【TypeScript】型引数を難しく捉えていたけど、関数の引数と変わらないじゃん

# 1.はじめに

型引数というものがあるということは知っていましたが、  
イマイチよくわからないまま使っていました。

調べてみると、
「関数の引数と同じように使えるんだ。なんか難しく捉えていた
と感じました。

@uhyo さんの「プロを目指す人のための TypeScript 入門」を元にキャッチアップしました。


間違って解釈している所ありましたら、ご指摘いただけますと幸いです。

# 2.目次

[1. はじめに](#1はじめに)  
[2. 目次](#2目次)  
[3. この記事でわかること](#3この記事でわかること)  
[4. 環境](#4環境)  
[5. 型引数](#5型引数)  
　[5.1. 型引数とは？](#51型引数とは)  
　[5.2. 複数の型引数](#52複数の型引数)  
　[5.3. 型引数を持つ型を使う](#53型引数を持つ型を使う)  
　[5.4. 型引数に型を入れて使う](#54型引数に型を入れて使う)  
　[5.5. 型引数の制約](#55型引数の制約)  
　[5.6. デフォルトの型引数](#56デフォルトの型引数)  
　[5.7. 制約とデフォルトを同時に使う](#57制約とデフォルトを同時に使う)  
[6. おわりに](#6おわりに)  
[7. 参考](#7参考)  


# 3.この記事でわかること

:::note info

型引数の基礎について理解できます。

- 型引数の使いかた
- 型引数に制約を与える使いかた
- 型引数のデフォルト型の使いかた

:::

最終的にこのコードを理解することができます。

```ts
type Engineer = {
    name: string;
    year: number;
    frontSkillList?: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    }
    backSkillList?: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    }
};

type FrontendEngineer = {
    name: string;
    year: number;
    frontSkillList: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    }
};

type ProjectManager = {
    name: string;
    year: number;
    frontSkillList: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    }
    backSkillList: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    }
    isPM: boolean;
};

type EngineerTeam<Programer extends Engineer, PM extends Engineer = ProjectManager> = {
    teamName: string;
    employee: Programer;
    director: PM;
};

type FrontTeam = EngineerTeam<FrontendEngineer>;
```


# 4.環境

- TypeScript: 4.7.4
- Node.js: 16.15.1



# 5.型引数

## 5.1.型引数とは？


型引数とは、型を定義する時に引数（パラメータ）を持てすことができる型です。

引数は宣言の中でだけしか使えません。

型引数を宣言するには、型名のあとに型引数を `< >` で囲うことで型引数をもった方を宣言できます。


言葉だけなれべてもイメージがわかないかと思いますので、  
実際にどのように使うかを簡単な例で見ていきたいと思います。

```ts:
type Engineer<T> = {
    name: string;
    age: number;
    skillList: T;
};
```

- `Engineer<T>` が型引数を持った型（ジェネリック型）
- `< >` の中の `T` が型引数
- `skillList: T;` の部分が、`skillList` プロパティは `T` 型

という風に使用します。

つまり `Engineer<T>` 型は、
- `name` プロパティは、`string` 型
- `age` プロパティは、`number` 型
- `skillList` プロパティは、`T` 型

のオブジェクトの型ということになります。

今回は、型引数を `T` としましたが、これは任意でつけることができるため、`SkillList` 型という風にもできます。

```ts:
type Engineer<SkillList> = {
    name: string;
    age: number;
    skillList: SkillList;
};
```

## 5.2.複数の型引数

型引数は、複数設定することができます。

```ts:
type Engineer<FrontSkillList, BackSkillList> = {
    name: string;
    age: number;
    frontSkillList: FrontSkillList;
    backSkillList: BackSkillList;
};
```

なんか説明がくどくなっちゃいますが、（そんなん説明せんでもわかってるわ！って方は飛ばしてください）  

Engineer<FrontSkillList, BackSkillList> 型は、
- `name` プロパティは、`string` 型
- `age` プロパティは、`number` 型
- `frontSkillList` プロパティは、`FrontSkillList` 型  
- `backSkillList` プロパティは、`BackSkillList` 型

のオブジェクトの型ということになります。


## 5.3.型引数を持つ型を使う

型引数の宣言を見てきましたが、  
「じゃあこれをどうやって使うんだろう🤔」  
となるかと思います。

ここから、型引数を持つ型の使いかたを見て聞きたいと思います。

```ts:
type Engineer<FrontSkill, BackSkill> = {
    name: string;
    year: number;
    frontSkill: FrontSkill;
    backSkill: BackSkill;
};

// Engineer<FrontSkill, BackSkill> 型を使ったオブジェクト
const kakedashiEngineer:Engineer<string, string> = {
    name: 'kakedashi',
    year: 0,
    frontSkill: 'JavaScript',
    backSkill: 'PHP'
};
```
 
`kakedashiEngineer:Engineer<string, string>` というように使います。

これは、型宣言した `Engineer<FrontSkill, BackSkill>`に   
- `FrontSkill` の型引数に `string` 型
- `BackSkill` の型引数に `string` 型

を代入するという意味になります。

`kakedashiEngineer` は、
- `frontSkill` プロパティは `string` 型となり、`'JavaScript'` という値
- `backSkill` プロパティは `string` 型となり、`'PHP'` という値
 
が入っているオブジェクトとなります。

つまり、

```ts:
type Engineer = {
    name: string;
    year: number;
    frontSkill: string;
    backSkill: string;
};
```

という型と同じ意味になります。

このように、  

:::note info

型宣言する際には具体的な型を宣言せずに、使用する際に型を定義します。

:::

使いたい用途に合わせて型を宣言できます。


型引数を持った型を使う上で注意があります。  

:::note warn

型引数を持った型を使用する場合は、型引数を用いなければエラーとなります。

:::

どういうことかといいますと、

```ts:
type Engineer<FrontSkill, BackSkill> = {
    name: string;
    year: number;
    frontSkill: FrontSkill;
    backSkill: BackSkill;
};

// Engineer<FrontSkill, BackSkill> 型を使ったオブジェクト
const kakedashiEngineer:Engineer = {
    name: 'kakedashi',
    year: 0,
    frontSkill: 'JavaScript',
    backSkill: 'PHP',
};
```

ということです。
これをコンパイルすると

:::note alert

Generic type 'Engineer' requires 2 type argument(s).

> ジェネリック型の 'Engineer' は2つの型引数を必要とします。

::: 

と注意されてしまいます。

（デフォルト値を設定していれば、使用時に型引数を省略することができます。  [ → 参照：デフォルトの型引数](#56デフォルトの型引数)）


## 5.4.型引数に型を入れて使う

使用する際に、型引数に型を入れて使うこともできます。


```ts:
type Engineer<FrontSkillList, BackSkillList> = {
    name: string;
    year: number;
    frontSkillList: FrontSkillList;
    backSkillList: BackSkillList;
};

type EngineerSkillList = {
    languageList: Array<string>;
    frameworkList: Array<string>;
};

const kakedashiEngineer:Engineer<EngineerSkillList, EngineerSkillList> = {
    name: 'kakedashi',
    year: 0,
    frontSkillList: {
        languagList: ['JavaScript'],
        frameworkList: ['React'],
    },
    backSkillList: {
        languageList: ['PHP'],
        frameworkList: ['Laravel'],
    },
};
```

`FrontSkillList` と `BackSkillList` の方引数に `EngineerSkillList` 型を代入しています。

`frontSkillList`, `backSkillList` プロパティはそれぞれ `EngineerSkillList` 型ということになります。

つまり、

```ts:
type Engineer = {
    name: string;
    year: number;
    frontSkillList: EngineerSkillList;
    backSkillList: EngineerSkillList;
};
```

という型と同じ意味になります。

更にこの型を分解してみると、

```ts:
type Engineer = {
    name: string;
    year: number;
    frontSkillList: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    },
    backSkillList: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    },
};
```

という型になります。

## 5.5.型引数の制約

次に制約を与えた型引数を見ていきます。

型引数に **ある型の部分型でなければいけない** という制約です。

部分型に関しては、以前記事を書きましたのでご参考にどうぞです。  
（ご覧いただいた際についでに **いいね** してくださると嬉しいです😆 w）

https://qiita.com/daishiman/items/a0573f2ae7f0e76d1895


では、どのように制約を与えるのかといいますと、  
型引数の宣言の後ろに `extends 型` と宣言します。

```ts
type Engineer<FrontSkillList extends LanguageSkillList, FrontSkillList extends LanguageSkillList> = {
    name: string;
    year: number;
    frontSkillList: FrontSkillList;
    backSkillList: BackSkillList;
};
```

このように使います。

では、具体的なコードを見てみましょう。

```ts
// 型引数に制約を与えた型を宣言
type Engineer<FrontSkillList extends LanguageSkillList, FrontSkillList extends LanguageSkillList> = {
    name: string;
    year: number;
    frontSkillList: FrontSkillList;
    backSkillList: BackSkillList;
};

type LanguageSkillList = {
    languageList: Array<string>;
}

// LanguageSkillList 型の部分型
type LanguageAndFrameworkSkillList = {
    languageList: Array<string>;
    frameworkList: Array<string>;
}

// 型引数に制約を与えた型を別名 （Kakedashi） で宣言する
type Kakedashi = Engineer<LanguageSkillList, LanguageAndFrameworkSkillList>;

const kakedashiEngineer:Kakedashi = {
    name: 'kakedashi',
    year: 0,
    frontSkillList: {
        languageList: ['JavaScript'],
    },
    backSkillList: {
        languageList: ['PHP'],
        frameworkList: ['Laravel'],
    },
};
```

`FrontSkillList extends LanguageSkillList` と `BackSkillList extends LanguageSkillList` が制約を与えた型引数となります。

型引数 `FrontSkillList`、 `BackSkillList` に代入する型は`LanguageSkillList` の部分型でなければいけません。  

つまり、

:::note info

`LanguageSkillList` 型の性質を引き継いでいる引数を `FrontSkillList` や `BackSkillList` に代入しなければいけません  

:::

ということです。

下記の内容から、`LanguageAndFrameworkSkillList` 型は `LanguageSkillList` の部分型の関係ということがわかります。  
（なぜ？と思う方は[こちら](https://qiita.com/daishiman/items/a0573f2ae7f0e76d1895)の記事をご参考にしてください）

```ts
type LanguageSkillList = {
    languageList: Array<string>;
}

type LanguageAndFrameworkSkillList = {
    languageList: Array<string>;
    frameworkList: Array<string>;
}
```

次に、下記の内容から、
- 型引数の `FrontSkillList` に `LanguageSkillList` を代入  
- 型引数の `BackSkillList` に `LanguageSkillList` の部分型の `LanguageAndFrameworkSkillList` を代入

ということがわるかと思います。

```ts
type Kakedashi = Engineer<LanguageSkillList, LanguageAndFrameworkSkillList>;
```

これが制約となります。

したがって、制約にない型引数を代入するとエラーとなります。

```ts:
type Engineer<FrontSkillList extends LanguageSkillList, BackSkillList extends LanguageSkillList> = {
    name: string;
    year: number;
    frontSkillList: FrontSkillList;
    backSkillList: BackSkillList;
};

type LanguageSkillList = {
    languageList: Array<string>;
}

type Manager = Engineer<string, number>;
```

:::note alert

Type 'string' does not satisfy the constraint 'LanguageSkillList'.

（'string' 型は制約 'LanguageSkillList' を満たしません。）

:::


制約を与えることで型をより安全に使えますね。

## 5.6.デフォルトの型引数

[型引数を持つ方を使う](#53型引数を持つ方を使う) で、  
>型引数を持った型を使用する場合は、型引数を用いなければエラーとなります。

と記述しましたが、使用する際に型引数を省略することができます。

それが、デフォルトの型引数です。

では、型引数のデフォルトの型について見ていきます。

これは、型引数を省略してもデフォルトの型を使ってくれるものになります。

型引数の後ろに `= 型` と宣言して使います。  
この `型` の部分がデフォルト値となります。

```ts:
type EngineerTeam<Programer = Engineer, PM = Engineer> = {
    teamName: string;
    director: PM;
    employee: Programer;
}
```

このように使います。

では、具体的に見ていきましょう。

```ts
type Engineer = {
    name: string;
    year: number;
    frontSkillList?: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    }
    backSkillList?: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    }
};

// 型引数にデフォルトの型を宣言
type EngineerTeam<Programer = Engineer, PM = Engineer> = {
    teamName: string;
    employee: Programer;
    director: PM;
};

// EngineerTeam<Engineer, Enginerr>と同じ
type FrontTeam = EngineerTeam;

const frontTeam: FrontTeam = {
    teamName: 'ふろんとチーム',
    employee: {
        name: 'かけだし',
        year: 1,
        frontSkillList: {
            languageList: ['JavaScript'],
            frameworkList: ['React'],
        },
    },
    director: {
        name: 'ぶちょう',
        year: 20,
        frontSkillList: {
            languageList: ['JavaScript', 'TypeScript'],
            frameworkList: ['Next.js', 'Nuxt.js'],
        },
        backSkillList: {
            languageList: ['PHP', 'Ruby'],
            frameworkList: ['Laravel', 'Ruby on Rails'],
        }
    }
};
```

ではまずは、デフォルトの型を宣言している部分から見てみます。

```ts
// 型引数にデフォルトの型を宣言
type EngineerTeam<Programer = Engineer, PM = Engineer> = {
    teamName: string;
    employee: Programer;
    director: PM;
};
```

これは、
- 型引数の `Programer` に何も代入されなかったら `Engineer` 型を使う
- 型引数の `PM` に何も代入されなかったら `Engineer` 型を使う

という意味になります。

では、デフォルト型を使ってみましょう。

```ts
type FrontTeam = EngineerTeam;
```

`FrontTeam` 型は `EngineerTeam` 型を使いますが、引数がありません。  
なので、これは、引数にデフォルト型が代入されることになります。


```ts
type FrontTeam = EngineerTeam<Engineer, Enginerr>;
```

と同じことになります。

片方の型引数にのみ型を代入することもできます。

```ts:
type FrontTeam = EngineerTeam<string>;
```

これは、

```ts
type FrontTeam = engineerTeam<string, Engineert>;
```

と同じです。

ただ、今のところは、右にだけ指定することはできませんのでデフォルト型を設定するときは左に型引数を設ける必要があります。

## 5.7.制約とデフォルトを同時に使う

制約とデフォルトを同時に使うこともできます。

このように使います。  
少しややこしそうに見えますが、今まで見てきたことをまとめただけになります。


```ts
type Engineer = {
    name: string;
    year: number;
    frontSkillList?: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    }
    backSkillList?: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    }
};

type FrontendEngineer = {
    name: string;
    year: number;
    frontSkillList: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    }
};

type ProjectManager = {
    name: string;
    year: number;
    frontSkillList: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    }
    backSkillList: {
        languageList: Array<string>;
        frameworkList: Array<string>;
    }
    isPM: boolean;
};

// 型引数に制約とデフォルト型を宣言
type EngineerTeam<Programer extends Engineer, PM extends Engineer = ProjectManager> = {
    teamName: string;
    employee: Programer;
    director: PM;
};

// EngineerTeam<FrontendEngineer, ProjectManager>と同じ
type FrontTeam = EngineerTeam<FrontendEngineer>;

const frontTeam: FrontTeam = {
    teamName: 'ふろんとチーム',
    employee: {
        name: 'かけだし',
        year: 1,
        frontSkillList: {
            languageList: ['JavaScript'],
            frameworkList: ['React'],
        },
    },
    director: {
        name: 'ぶちょう',
        year: 20,
        frontSkillList: {
            languageList: ['JavaScript', 'TypeScript'],
            frameworkList: ['Next.js', 'Nuxt.js'],
        },
        backSkillList: {
            languageList: ['PHP', 'Ruby'],
            frameworkList: ['Laravel', 'Ruby on Rails'],
        },
        isPM: true,
    }
};
```

では、型宣言しているところから見てみましょう。

```ts
// 型引数に制約とデフォルト型を宣言
type EngineerTeam<Programer extends Engineer, PM extends Engineer = ProjectManager> = {
    teamName: string;
    employee: Programer;
    director: PM;
};
```

この部分ですね。

- 型引数 `Programer` 
  - `Engineer` 型の部分型（制約）
- 型引数 `PM` 
  - `Engineer` 型の部分型（制約）
  - デフォルト型は `ProjectManager` 型

と意味になります。

ややこしそうに見えますが、単純ですね。

この型を使っているのが、

```ts
type FrontTeam = EngineerTeam<FrontendEngineer>;
```

になります。

これは、

```ts
type FrontTeam = EngineerTeam<FrontendEngineer, ProjectManager>;
```

と同じことです。

# 6.おわりに

型引数の使いかたの理解が深まりました。

今までわけも分からず使っていたフシがあったので、  
これからは内容を理解して上で使えそうです。

というか、型引数を使わないと不便だなと感じるようになりましたw

併せて他の記事も読んでいただけると嬉しいです🙇‍♂️

https://qiita.com/daishiman/items/5acf803f252b82f970fe

https://qiita.com/daishiman/items/d382279a2a6cae3ea1aa

https://qiita.com/daishiman/items/42ba60a223cadbd98104

https://qiita.com/daishiman/items/a0573f2ae7f0e76d1895

最後まで読んでいただきありがとうございました。

# 7.参考

[書籍：プロを目指す人のためのTypeScript入門　鈴木僚太［著］](https://twitter.com/uhyo_/status/1517158176365641729?s=20&t=5jZBSZVbLrAsgkCUruO7Ig) @uhyo