こちらは、アソビュー! Advent Calendar 2021の18日目の記事です。
こんにちは、アソビュー!でエンジニアをしている島田です
そろそろクリスマスがやって来ますが、大切な人に渡すプレゼントはお決まりでしょうか!?私は、2歳になる子供に滑り台付きのジャングルジムを上げようと思い品切れになる前に購入したのですが、いざ届くと早く上げたくなり上げてしまいました😅
さて、今回はTypeScriptのジェネリクスについて学んだので備忘も兼ねて紹介できればと思います。
弊社では週に一度のペースでフロントエンドに関わる人が集まって勉強会のようなことを行なっています。そこでTypeScriptのジェネリクスの話題となったのですが、私自身よく分かっていないなと思ったので調べてみました。
はじめに
ジェネリクスはTypeScriptだけに限った機能ではありません。あらかじめ特定の型に定義せず、曖昧な状態のままで定義することができます。
そして利用時点で型指定を行い、特定の型に確定します。このように利用されるまでは型定義が曖昧で様々な型で利用されることから「総称型」とも呼ばれます。
ジェネリクスは利用法が色々あるみたいなので、学んでいきたいと思います。
メソッドでの利用
以下のコードは取得したデータをjson形式に変換してから返却するfetchAPIのラップ関数です。
getUserはfetcherを利用して取得したユーザー情報を利用元に返却します。
上記のコードだと返却値の型はPromise<any>
という型になりTypeScriptの型チェックの恩恵を受けられません。
ここで、使用するのがジェネリクスです。
ジェネリクスを使用することでany型を返却しなくても良くなりました。
さらに、ジェネリクスを用いることでgetUserだけに依存することなく、その他のデータ取得関数からも利用可能な汎用的なfetchAPIのラップ関数として定義することができます。
上記の例は戻り値だけでしたが、引数にもジェネリクスを使用することができます。
複数の引数を持つ関数での利用
複数の引数を持つ関数に個別のジェネクスを定義することもできます。
インターフェースでの利用
インターフェースにもジェネリクスを利用できます。
インターフェースを継承したジェネリクスでの利用
ジェネリクスはどの型も使用することができますが、内部のプロパティまで理解してくれるわけではありません。例えば、以下のコードのようにarg
のlength
を使いたいとした場合エラーとなります。
なぜなら、全ての型に特定のプロパティが存在するとは限らないためです。これを解決するためにTypeScript側に特定のプロパティが存在することを確定させる制約をかけることができます。
上記のように記述することでインターフェースのプロパティを持っているジェネリクスを定義することができます。ちなみに、インターフェースを継承しただけなので length
というプロパティを持っていれば他のプロパティも渡すことができます。
まとめ
ジェネリクスを学んでみて以下のような場面で有効に使えるなと思いました。
- コンパイル時型チェックの恩恵を捨てるようなコード(any型、あるいは型キャストの使用)
- 型だけ違う実装の大量生産(再利用したい時)
ジェネリクスをこれから学ぼうとしている方に少しでも参考になれば幸いです。
最後に
アソビューでは「生きるに、遊びを。」をミッションに、一緒に働くメンバーを募集しています!ご興味がありましたらお気軽にご応募いただければと思います!