TypeScriptでジェネリクス(Generics)を学ぼう!

こんにちは。KOUKIです。

TypeScriptのジェネリクスについて、概要や使い方をまとめました。

環境構築

以下の記事で作成したプロジェクトを使います。
※TypeScriptが動けば問題ありません

ジェネリクス(Generics)とは

ジェネリック型の利用目的 1: TypeScriptにおける型の安全性を高めることができる
2: 自動保管等の開発サポートを向上することができる
3: 型に柔軟性を持たせることができる

ジェネリクスは、平たく言うと「」です。ジェネリック型です。

他の特定の型と結合されてできています

配列を元に考えるとわかりやすいです。

上記のコードは、「string型の配列」です、

配列であるため、Array型を型として指定することができるはずです。

しかし、上記のコードでは、次のエラーが発生します。

TypeScriptはArray型が格納する値が、どのような型かをチェックしません。そのため「どんな型であるか」を指定する必要があります。

このように、ジェネリック型は追加の型情報を提供できる型です。

ジェネリクスは、TypeScriptの機能であるため、JavaScriptには存在しません。ただし、ジェネリクスの概念は、JavaやC#など他の言語にも存在します。

配列を例に説明しましたが、配列だけに指定できるわけではなく、関数やクラスなどにも指定できます。

ジェネリック関数

最初にジェネリックを使った関数について、触れます。

まずは、ジェネリック無しの関数を定義しましょう。

上記のコードでは、オブジェクトを引数としてmerge関数に渡しています。merge関数では、それぞれのプロパティをマージしたオブジェクトを呼び出し元に返しています。

この状態で、プロパティの一つである「name」にアクセスすると以下のエラーが発生します。

TypeScriptは、mergeObjの中に「name」プロパティが存在していることをチェックすることができないようです。

関数に渡しているobject型は、型としては非常に曖昧なもので、オブジェクトであれば何でも渡せてしまいます。そのため、このままだとTypeScriptはオブジェクトに存在するプロパティを判別することができません。

この場合、ジェネリックスを利用すると便利です。

先ほども説明しましたが、ジェネリックス型は「後から型を追加できる」型です。

merge関数を以下のように修正しましょう。

不思議に感じるかもしれませんが、これでエラーが消えます。

ジェネリックスをつけた場合は、merge関数は曖昧なobject型ではなく、明確に交差型(objA, objB)を返すことを理解します。

面白いのは、この型は、呼び出し元の引数の指定で決まるということです。

つまり、以下のように「柔軟な」コードがかけます。

KOUKI
KOUKI

呼び出した時に、初めて型が決まりますね。

ジェネリックスに制約を追加する

ジェネリックスにより柔軟なコードを書くことができるようになりましたが、逆に柔軟すぎて簡単にバグを生産できてしまいます

上記は、第二引数に31(number)を渡してますが、エラーを発生させません。これは問題です。

ジェネリックス型は、「extends」キーワードを使用することで、制約を追加することができます。

制約を追加したため、エラーを検出できるようになりました。

尚、制約にはnumberやstring、クラス、ユニオン型など様々な型を指定できます。

ジェネリック型は曖昧さを残す

ジェネリック型の概念は少し難しく感じるかもしれません。しかし、慣れるとかなり便利に使えます。

例えば、ジェネリック型は関数の定義時に型のタイプを「決定づけない」ことが可能です(曖昧さを残す)。

例えば、パラメータをカウントする関数があるとします。

lengthプロパティを持つinterfaceをcountParameterのTに実装しました。

このようにすると、関数の呼び出し元では、lengthプロパティを元々持っているstring型やArray型を引数として渡すことができます。

逆にlengthプロパティを持っていないnumber型やboolean型などを渡した場合は、エラーになります。

このようにジェネリック型を使って曖昧にしておくと、柔軟に呼び出すことが可能です。

keyofで制約をつける

keyofを使うとオブジェクトが特定のプロパティを持っていることを保証できるようにできます。

例えば、以下のオブジェクトがあるとします。

この場合は、bookオブジェクトがtitleを持っていることを保証したいです。

そこで、keyofを使って制約をかけます。

これで、Bookにtitleが存在する制約をつけることが可能になりました。これは、結構便利なんです。

ジェネリッククラス

ジェネリックは、クラスにも使うことが可能です。

例えば、以下のようなクラスがあるとします。

このクラスは、なんらかのデータを保存したり、削除したり、取得したりすることができます。

dataやitemには型指定を指定ないので、現時点ではエラーが表示されます。

通常なら型を指定する必要がありますが、ジェネリック型を使うことで、柔軟なデータを渡せることができるようになります。

上記では、T型(文字列はT出なくても良い)を指定しました。これで、number型、string型、boolean型など型のタイプを気にせず、コードを呼び出せるようになりました。

便利そうですよね。

ちなみにクラスに対しても制約をつけることが可能です。

おわりに

ジェネリック型については、以上になります。

ジェネリック型を使用することで、呼び出し元で型を自由に決定つけることができるので、大変便利です。

実際のプロジェクトでもガンガン使っていければと思います^^

それでは、また!

関連記事

参考書籍