ジェネリクスをもう少しだけ使いこなす。

[**4歳娘「パパ、具体的な名前をつけないで？」**](https://qiita.com/Yametaro/items/bcd3166e5d2ad696a89e)

↑こちらの記事の続きです。

##  前回のあらすじ

前回の記事は、

> + 引数が**数値**であれば、**数値の配列**を返すべし
> + 引数が**文字列**であれば、**文字列の配列**を返すべし
> + 引数が**オブジェクト**であれば、**オブジェクトの配列**を返すべし
> + 引数が**配列**であれば、**配列の配列**（二次元配列）を返すべし

上記のような**制限**を持った関数を、
**ジェネリクス**を使って表現しよう、という内容でした。

コードも軽く見返してみましょう。

```typescript
function toArray<T>(arg: T): [T, T, T] {
  return [arg, arg, arg];
}
```

できあがった関数は上記のようなものでした。

> + 引数が`T`だったら、戻り値は`[T, T, T]`であるべし

つまり、

> + 引数が`number`だったら、戻り値は`[number, number, number]`であるべし

このような制限を、**ジェネリクス**で表現していましたね。

## いただいたコメント

前回の記事を読んだあるユーザーさんから、
こんなコメントをいただきました。

> 娘の友達「**文字列と数値とオブジェクトと数値の配列**を受け取れる関数
ってお願いしたのに、**真偽値も浮動小数点数もnullも受け取れる**じゃない。
それどころか**何でも受け取れるわ**。これでは使えない。困ったわ」

私は以下のように回答しました。

> ガキが・・・
舐めてると潰すぞ

もちろん、潰すというのは冗談です。
少しも潰しません。

## もっと制限したい

先ほどのコードは、

> + 引数と同じ型の値からなる配列を返すべし

という要件は満たしていますが、

> + 受け取ることができる引数は、  
**文字列**または**数値**または**オブジェクト**または**数値の配列**のみとすべし

という条件は満たしていません。

2つの制限を両方とも**型で保証**するために、**extends**を使ってみましょう。

まず、**文字列と数値とオブジェクトと数値の配列**を許容する**型エイリアス**を定義します。

```typescript
type Hage = string | number | object | number[]
```

これは、

> `Hage`型は、
**文字列**または**数値**または**オブジェクト**または**数値の配列**ですよ

という意味です。

そして次は、

> `T`という何らかの型は、`Hage`型を継承したものであるべし

ということを表現していきましょう。

```typescript
function toArray<T extends Hage>(arg: T): [T, T, T] {
  return [arg, arg, arg];
}
```

このように書きます。
`<T extends Hage>`の部分が、

> + `T`は`Hage`型を継承すべし

ということを表しています。

これで、関数`toArray`は───

> + 引数と同じ型の値の配列を返すべし

上記の条件に加え、

> + 受け取る引数は、  
**文字列・数値・オブジェクト・数値の配列**のどれかにすべし

───という制限も持つことができました。

試しに**真偽値**を渡してみます。

```typescript
const trueArray = toArray(true);
```

すると、VSCodeに───

> 型`true`の引数を、型`Hage`のパラメーターに割り当てることはできません。

───こんなエラーメッセージが表示されました。
上手くいっているようです。

ちなみに`Hage`という型エイリアスを定義せずに、`<T extends string | number | object | number[]>`というジェネリクスを書いても同じ効果が得られます。

## もっと細かい制限もできる

例えばこんな関数があるとします。

```typescript
function getFullNamePersonList(persons: User[]): User[] {
  const fullNamePersonList =
    persons.filter(person => person.firstName && person.familyName);
  return fullNamePersonList;
}
```

ユーザ情報が入った配列の中から、
姓と名を両方持つユーザだけを**抽出する**関数です。
姓か名を**空文字**で登録しているユーザは取り除かれます。

この`getFullNamePersonList`関数は───

> + `User`の配列を受け取って、`User`の配列を返すべし

───といった型付けになっています。

`User`は以下のような`interface`です。

```typescript
interface User {
  firstName: string
  familyName: string
  tel: number
}
```

これは───

> + ユーザは、姓と名と電話番号を持つべし

───といった意味になります。

`getFullNamePersonList`関数は、引数として`User[]`しか受け取れないため、
間違って他の型の値を渡してしまって、
関数が正しく実行できずにエラーになってしまう、といった事故を防げます。

## しかし、ユーザだけでなくライターさんも登場

ここで`User`以外に、`Writer`という
**姓と名**を持つ概念が登場したとします。

`Writer`は以下のような`interface`です。

```typescript
interface Writer {
  firstName: string
  familyName: string
  articles: article[]
}
```

> + ライターは、姓と名と**記事**（配列）を持っている

上記のようなイメージです。
持っているプロパティが`User`と微妙に違います。

> + でも、`Writer`の配列にも`getFullNamePersonList`関数を使いたい
+ 姓と名があるのは同じだからね

という状況だとします。

このような場合に、

```typescript
interface HasFullName {
  firstName: string
  familyName: string
}
```

上記のような`HasFullName`という`interface`を定義して、
ジェネリクスを書く際に使用することができます。

`HasFullName`は、
`getFullNamePersonList`関数に必要なプロパティ、
つまり、フルネームを作るのに必要なプロパティだけを定義した`interface`ですね。

> + 姓と名を持つべし

といったイメージです。

`getFullNamePersonList`関数の型付けは以下のようにします。

```typescript
function getFullNamePersonList<T extends HasFullName>(persons: T[]): T[] {
  /* 省略 */
}
```

> + 引数は、`HasFullName`を継承した`T`型の値からなる配列でなくてはならないよ

つまり、

> + 引数には、`firstName`と`familyName`を持ってるやつ（の配列）を渡さないといけないよ
> + 戻り値も、`firstName`と`familyName`を持ってるやつ（の配列）を返さないといけないよ

という制限を表現できます。

関数を呼び出す時は、以下のようなコードになります。

```typescript
const fullNameUserList = getFullNamePersonList<User>(users);
const fullNameWriterList = getFullNamePersonList<Writer>(writers);
```

なお、関数呼び出し時に`<User>`や`<Writer>`を省略したとしても、
コンパイラによる**型推論**が働くため、正しく実行可能です。

## `HasFullName`インターフェイスを定義しなくてもいい

`HasFullName`インターフェイスを使わない書き方だと、

```typescript
type Person = User | Writer
```

上記のように、`User`と`Writer`を許容する
`Person`という型エイリアスを定義して、以下のように書くこともできます。

```typescript
function getFullNamePersonList<T extends Person>(persons: T[]): T[] {
  /* 省略 */
}
```

> + 引数も戻り値も`Person[]`型じゃないといけないよ

つまり、

> + 引数には、`User[]`か`Writer[]`を渡さないといけないよ
> + 引数と同じ型の戻り値を返さないといけないよ

というイメージですね。

## `HasFullName`を定義したほうが、コードの説明書代わりとして良いかも

型エイリアス`Person`を使用しても良いのですが、
`getFullNamePersonList`関数の型付けに使用するジェネリクスとしては、
`HasFullName`を定義して`<T extends HasFullName>`と書く方が

> + 姓と名が要るんやで！

という感じを表現できていて、ワイのようなザコーダーにとっては
脳死で読めて楽かもしれません。

`getFullNamePersonList`の引数としては、
`人間`というより`姓と名を持ったやーつ`が必要ですもんね。

```typescript
interface HasFullName {
  firstName: string
  familyName: string
}
```

コード内に上記の`HasFullName`インターフェースが書いてあると、

> ワイ「あー、フルネームを持ってる人を選別する関数やから」
ワイ「`firstName`と`familyName`を持ってることが要点なんやね」

上記のような**ドキュメント的な**働きをしてくれるかもしれません。

## ジェネリクスで色々なルールを表現できた
このように、ジェネリクスを使用すると

> + 引数と返り値は同じ型であるべし

だけでなく、さらに

> + 姓と名を持った何らかの型を受け取るべし

といったことも表現できるようになります。

**何らかの型**、という自由な感じを持ちつつも、

> + `firstName`と`familyName`は持っていること！

といった**具体的な制限**を加えることができます。

## まとめ

大きめの案件で、様々なオブジェクトを扱う場面が増えると
本当に訳が分からなくなってくることがあります。

そんな時に、型やジェネリクスがあると、例えば

> + 姓と名を持った何らかの型を受け取って、返すべし

とか、
色々と**取り締まってくれる**ので、`undefined`エラー等が発生しにくく、
開発が捗ります。

ブラウザ上で実行する前に、エディタ上で

> 「君、違うことしてるで！」

って注意してもらえる訳ですもんね。

面倒くさいようで、ないと逆に困るものだと思います。

**〜おしまい〜**