はじめに
社内向けの教育資料を転載。
以下本文。
ジェネリクスの特徴
- 三角カッコの不思議なやつ
- ライブラリとか覗くとよく生息してる
- エラーのトレースバックでもよくいる
- 初心者殺し!!!
ジェネリクスを一言で
型が違うが動作がほぼ同じ関数・オブジェクトを型の違いを気にせず定義する機能。
むずかしい...
具体例
いろんな型のデータに対して+
操作する関数を考える。
const addInt = (a: number, b: number) => a + b
addInt(1, 2) // 3
const addStr = (a: string, b: string) => a + b
addStr("a", "b") // "ab"
const addArr = (a: any[], b: any[]) => a + b
addArr([1, 2, 3], [4, 5]) // [1, 2, 3, 4, 5]
なんか型が違うだけでほとんど同じ操作してる気がする...
3回もほぼ同じこと書いてるし...
めんどくさい...
と昔の偉いエンジニアは考えた。
めんどくさいときは共通化してサボるのが鉄則。
では共通点とは?相違点は?
共通点
2つのデータに対して+
操作を行う
相違点
入ってくる2つのデータの型が違う。
すなわち、a
とb
という両方型が同じで何らかの型を持つ、その型を仮にT
と呼ぼう、が入ってくると戻り値の方もT
になる。
共通点をくくりだしてまとめる
共通化するとき各ケースで異なるものはどう取り扱うだろう。
例えば何回も使用する共通の処理をまとめたものである関数では、入力値が毎回異なるので"引数"という概念で相違点に柔軟に対応する。
実はジェネリクスはこの引数みたいなもの。
違うのは普通の引数がデータに対しての変数であるのに対し、ジェネリクスは型に対しての引数であるということ。
実際に上の3つの関数をジェネリクスを用いて再定義してみよう。
const add<T>(a: T, b: T) => a + b
add<number>(1, 2) // 3, number型を受け取ってnumberを返す
add<string>("a", "b") // "ab", string型を受け取ってstringを返す
add<any[]>([1, 2, 3], [4, 5]) // [1, 2, 3, 4, 5], any[]型を受け取ってany[]を返す
こうやって<T>
を型引数として宣言し、使用時に型を代入する。
上で説明したように、addはまさにT
型の変数a
とb
を受け取ってT
型の戻り値を返す関数に様変わりした。
こうして鬱陶しいaddの定義は一回ですみ、共通化の安寧がもたらされた。
まとめ
ジェネリクスはライブラリ使用時など必ず出てくる概念。
ある程度は知らずにコードを書けるが、ある時点で必ず成長がとまる。
だから早めに習得して、みんなもTypeScriptで気持ちよくなろう!
挑戦問題
実は上でジェネリクスを用いて共通化したadd
関数はエラーを起こす。
何故かわかるかな?