TypeScript 学習記録 #5(ジェネリック型(ジェネリクス))

commits4 min read読了の目安(約4000字

TypeScript 学習記録 #4 の続編。
今回のメインテーマ、「ジェネリック型について」

今回主に参考にした教材:

見返す用リスト(主に自分用)

TypeScript基礎学習を完走したので、いつでも見返すことの出来るようにリスト化しておく(5/17)

ジェネリック型

以下のことについて学んだ。

  • ジェネリック型
    • ジェネリクス(Generics)とも。「総称型」の意。
    • 用途:抽象的な型引数を用いて型を柔軟に指定することで、型を付けた関数などの再利用ができるようになる。「型の種類は異なるが同じデータ構造を持っている」関数がある場合などに、型の部分のみを抽象化して関数を1つに共通化するイメージ。
    • 注意:あくまで、何かを共通化するための手段であって、開発の始めからこのジェネリック型を使おう使おうと意識するようなものではない。始めのうちはリファクタリング時に検討するぐらい?
  • ポリモーフィズム(多態性、多相性)
    • 「色々な形に変化できる」ということ
    • ジェネリック型を用いることで、型を指定するまでは色々な形になることができ、呼び出す際に具体的な型を渡す(型をバインドする)ことで様々な型の使い方をすることができる。まさに多態性。

ジェネリック型を用いた例

ジェネリック型を用いたサンプル。詳しくは動画参照。

// ジェネリック型で共通化できそうな場面の例
// 以下の2つの関数は、まさに「型の種類は異なるが同じデータ構造」という条件に当てはまっている
const stringReduce = (array: string[], initialValue: string): string => {} // ①
const numberReduce = (array: number[], initialValue: number): number => {} // ②

// これをジェネリック型で共通化する(上2つの型定義の抽象化)
// 型の種類を適当な文字で置いて自由に代入出来るようにしておき、使う時に型を引数として渡すようなイメージ
// ジェネリック型には T,U,P,V などのアルファベットの大文字1文字が慣習的に使われるらしい
type GenericReduce<T> = {
  (array: T[], initialValue: T): T
}

// ここでは実際に<string>や<number>として、型として指定している(「型をバインドする」というみたい)
const genericStringReduce: GenericReduce<string> = (array, initialValue) => {} // ①と同じ意味
const genericNumberReduce: GenericReduce<number> = (array, initialValue) => {} // ②と同じ意味

因みに、この中で出てきているreduceという単語の意味は「リスト(配列)等の引数を一つの値に集約させること」を意味している。
余談で、JavaScriptのreduceメソッドの使いどころは、「配列 → プリミティブ型(NumberやString)」に加工する時や、「配列 → オブジェクト」に加工する時に使うのが好ましいとのこと。
「配列 → 配列」に加工する場合は、mapfilterなどがふさわしい?(参考

ジェネリック型の宣言方法

ジェネリック型には幾つかの宣言方法がある。上で見たジェネリック型の宣言方法も含めて、よく使いそうなもののみ書き残しておく(詳しく知りたい方は動画参照)。
基本的には関数の型定義における呼び出しシグネチャと同様の書き方。

// 完全な呼び出しシグネチャ(上の例はこれ)
type GenericReduce<T> = {
  (array: T[], initialValue: T): T
}

// 呼び出しシグネチャの省略記法
type GenericReduce<T> = (array: T[], initialValue: T) => T;

extendsを用いたジェネリック型の型制限とデフォルト値

extendsを用いることで、ジェネリック型の型を指定する際に型の制限を設けることが出来るようになる。イマイチ使い道はまだよく理解できていない。
また、ジェネリック型の型定義の際にデフォルト値を設定しておくことができる。その場合は型を利用する際に型指定をしなくても自動的にデフォルトで設定した型が付く。

// `extends`と付けることで、指定できる型を`string`と`number`のみに限定する
type GenericsExtends<T extends string | number> = {
  item: T;
};

const generics1: GenericsExtends<string> = { item: "aiko" } // OK
const generics2: GenericsExtends<number> = { item: 1122 } // OK
const generics3: GenericsExtends<boolean> = { item: true } // NG

// デフォルト値の設定
type GenericsDefault<T = string> = {
  item: T;
};

const generics4: GenericsDefault = { item: "aiko" }; // ジェネリック型の型指定をしなくてもデフォルトの`string`型が適用されている

ここまでの感想

ジェネリック型について理解を深めた。すぐに使うことはないかもしれないが、今後似たような場面に出くわしたら積極的に使えるようになりたい。

このジェネリック型講座の次にある、トラハックさんの「【日本一わかりやすいTypeScript入門】TypeScriptで学ぶオブジェクト指向開発」の講座は、直接的なアウトプットにはすぐに結びつかないだろうと判断したため、2倍速で流し見するにとどめた。
一応クラスやインスタンス、プロパティやメソッド、コンストラクタなどの理解は最低限はあるので、また必要になった時に戻ってこようと思う。