😀

5分でわかるTypeScriptのジェネリクスの使い方【手を動かしながら覚える】

2020/09/19に公開

ジェネリクスは難しそうで敬遠していたのだけれどコーディング中のサイトでこれを使った方が楽にかけるんじゃ?と思いインプットも兼ねて記事にしてみた。

正直いって公式ドキュメントやコードサンプルを見ていてもよくわからなかった...この記事を見た人も手を動かせるようTypeScriptの直貼りリンクのcodesandboxで動かせる形で記事にしてみた

ジェネリクスとは何か

ジェネリクス、よく「型を抽象化させたもの」と解説されているがこれではピンとこなかった。ピンときた方はおそらくこの記事は合わないかもしれない。

自分が理解した範疇で表現するとジェネリクスとは「関数の中にある引数,戻り値や変数の型を後から好きな型で指定できる機能」といえると思う。
抽象化って言葉がなんだかハードルを高そうにしており難しいのかなと思い込んでいた。
だが極力シンプルな形で表してみると全く難しいことはなくあとはシンプルな形から枝葉の葉を生やしているだけだった。

ジェネリクスのシンプルなサンプル

const genFunc = <T>(x: T): T => x
const getResult = genFunc<number>(1);
console.log(getResult);

genFunc()関数にジェネリクスの機能を載せている。「 T 」は型専用の変数のようなものだ。
使い方としてはgenFuncの「 T 」に任意の型を渡すだけだ。

言い換えると、

genFunc<number>(1)

ここでnumberを型の引数として渡している。

このカッコ書き<>がジェネリクスの特徴で、<>が型のためだけに用意された引数の別枠みたいなものだと思うと理解しやすいかと。
(ちなみに< number >などと型を渡さずとも型推論してくれるのでなくても動作はする。気になる人は< number >を削除してcosole.log(typeof x)で確認してみると良い。)

もしここで分かりにくいなと思った人はTypeScript自体も始めたばかりな方かもしれない。
とりあえずJSの場合と比較してみると分かりやすいので参考程度にコードを置いておく

const genFunc = (x) => x
const getResult = genFunc(1);
console.log(getResult);

問:型に string を流し込んでみよう

表題の通り、手を動かそうとタイトル付けしてしまった以上は環境をcodesandboxで用意した。手を動かしてみてください。
先のサンプルを改変すればすぐにできます。
codesandbox: https://codesandbox.io/s/vigilant-rubin-wb7mu?fontsize=14&hidenavigation=1&theme=dark

const genFunc = <T>(x: T): T => x;
const getResult = genFunc<string>("hoge");
console.log(getResult);

複数の型を指定する方法

const genFunc = <T, U>(x: T, y: U): void => {
  console.log(`Tは${typeof x}型で中身は${x}です`);
  console.log(`Uは${typeof y}型で中身は${y}です`);
};
const getResult = genFunc<number, string>(1, "ハマ寿司");
console.log(getResult);

型の変数として新たにUを追加した。
複数の型を指定する方法は簡単だ。新たにUを追加したのでUに別途型を渡してあげれば良い

// 結果
Tはnumber型で中身は1です 
Uはstring型で中身はハマ寿司です 

genFuncへ新たにUを追加し、

const genFunc = <T, U>(x: T, y: U): void => {}

渡す引数ももう1つ増やす。

const getResult = genFunc<number, string>(1, "ハマ寿司");

ジェネリクスを扱う対象がクラスになっても同じようにT,Uなどに型を引数として渡していけば良い

問:genFuncにKを追加しよう

< T, U >に加えてKも追加して任意の型を指定してgenFunc関数内のconsole.logに出力してみよう。
先ほどのコードに追加してみよう。
codesandbox: https://codesandbox.io/s/weathered-wave-0b826?fontsize=14&hidenavigation=1&theme=dark

サンプルとしてboolを追加してみた。

const genFunc = <T, U, K>(x: T, y: U, z: K): void => {
  console.log(`Tは${typeof x}型で中身は${x}です`);
  console.log(`Uは${typeof y}型で中身は${y}です`);
  console.log(`Kは${typeof z}型で中身は${z}です`);
};
const getResult = genFunc<number, string, boolean>(1, "ハマ寿司", true);
console.log(getResult);
// 結果
Tはnumber型で中身は1です 
Uはstring型で中身はハマ寿司です 
Kはboolean型で中身はtrueです 

おまけ:代入できる型のパターンを絞る

先ほどの例までだと意図しない型を代入される可能性がある。
事前にジェネリクスを別の型を使って拡張させておくと意図しない型を代入されないよう防ぐことができる。

type genValue = number | string;
const genFunc = <T extends genValue>(x: T): void => {
  console.log(`Tは${typeof x}型で中身は${x}です`);
};
const getResult = genFunc<string>("スシロー");
console.log(getResult);

仮に genFunc< string > を genFunc< boolean > に変えると Type 'boolean' does not satisfy the constraint 'genValue'. と怒られます。

Reactでジェネリクスを使ってみた

僕の場合、React上の.tsxファイルで使用した。
ここまでの例の通りのアロー関数の使い方ではエラーが出てしまった。その代わり function関数 として関数を用意すれば大丈夫だった。

const genFunc = function<T>( x: T ): T {
  return x;
}

理由はわからないが、ジェネリクスの型が1つしか用意されていないとアロー関数ではエラーとなった。
言い換えると、

const genFunc = <T, _>( x: T ): T => {
  return x;
}

これなら動く。ダミーとして _ を用意しておくことでアロー関数でも表現できた。

所感

実はオライリー本でジェネリクスの解説をみて挫折した(ページをスルーした)。
これは正しいことを正しいまま可能な限り解説する書籍にありがちなのでそうした人が参考になる記事になったら良いかなと思う。

Discussion