🤔

【Typescript】ジェネリクスの仕組み

2022/12/05に公開

ジェネリクスとは

型を引数として受け取ることができる仕組み。
型を引数に取れることで、型だけが異なる同じ関数の複製などを避けられる👏🏻

ジェネリクスを用いると、型の安全性とコードの共通化を両立することができます。

サバイバルTypescript:ジェネリクス

下記の例では、取れる引数の値をnumber型にしている。
しかし、型を指定したままではstringやオブジェクトなど他の型を取りたい場合に利用できず、型が異なるだけの関数を作成しなければならない。

function copy(value: number): number {
  return value;
}
console.log(copy(11));

ジェネリクスを利用し、型を引数に取ることで、再利用性を高めることができる。
下記の例のように<T>と記述することで、そこに型の実引数が渡ってくる。
<T>部分は自由に記載できるが、慣習的にはTypeという意味の<T>にするよう。

function copy<T>(value: T): T {
  return value;
}
console.log(copy<string>("hello"));

複数取ることもできる

function copy<T, T1, T2>(value: T): T {
  return value;
}

copy<string>("hello")の型の実引数部分を省略できる。
省略しても、関数の実引数から型推論される。
下記の例では型の実引数を省略しているが、.で繋ぐと、引数で渡しているhelloから型推論され、stringが持つメソッドが補完で出てきていることがわかる。
スクリーンショット 2022-12-03 14.50.47.png

型パラメータ(引数)に制約をつける方法

extendsキーワードを使うと制約をつけられる。
下記の例では、{ name: string }とextendsしている。

function copy<T extends { name: string }>(value: T): T {
  return value;
}
console.log(copy({ name: "Quill" }));

プロパティ名をextendsの内容と異なるものにするとエラー
スクリーンショット 2022-12-03 18.00.40.png

keyof演算子

keyof オブジェクトの型とすることで、オブジェクトの型のキーを取り出して、返す。

keyofはオブジェクト型からプロパティ名を型として返す型演算子です。

サバイバルTypescript:keyof演算子

type K = keyof { name: string; age: number };

type Kはnameとageのユニオン型になっていることがわかる。
スクリーンショット 2022-12-04 18.18.02.png

Classに対してジェネリクスを使用する方法

classに対してもジェネリクスは使用できる。
下記は使用例。

class LightDatabase<T extends string | number | boolean> {
  private data: T[] = [];
  add(item: T) {
    this.data.push(item);
  }
  remove(item: T) {
    this.data.splice(this.data.indexOf(item), 1);
  }
  get() {
    return this.data;
  }
}
const stringLightDatabase = new LightDatabase<string>();
stringLightDatabase.add("Apple");
stringLightDatabase.add("Banana");
stringLightDatabase.add("Grape");
stringLightDatabase.remove("Banana");
console.log(stringLightDatabase.get()); // → [ 'Apple', 'Grape' ]

interfaceに対してジェネリクスを使用する方法

interface TmpDatabase<T> {
  id: number;
  data: T[];
}
const tmpDatabese: TmpDatabase<number> = {
  id: 3,
  data: [32],
};

ユーティリティ型(utility type)の紹介

ユーティリティ型とは、型のライブラリ。

ユーティリティ型(utility type)は、型から別の型を導き出してくれる型です。functionが実行時の世界の関数だとしたら、ユーティリティ型は型の世界の関数といったイメージです。

サバイバルTypescript:ユーティリティ型

上記サバイバルTypescriptで代表的なユーティリティ型を掲載してくれているのが、そのうちの一つであるPartialというユーティリティ型を使用した例。

Partial<T>は、オブジェクト型Tのすべてのプロパティをオプションプロパティにするユーティリティ型です。

サバイバルTypescript:Partial

interface Todo {
  title: string;
  text: string;
}
type Todoable = Partial<Todo>

全てのプロパティがオプションになっていることがわかる。
スクリーンショット 2022-12-05 22.03.53.png

Mapped Typesを使用する方法

Mapped Typesは型のfor文である。

例1)string型で展開

type MappedTypes = {
  [P in 'tomato' | 'pumpkin']: string
}

スクリーンショット 2022-12-05 22.19.42.png

例2)文字列リテラルで展開

type MappedTypes = {
  [P in 'tomato' | 'pumpkin']: P
}

スクリーンショット 2022-12-05 22.18.34.png

先頭にreadonlyをつけると全てのプロパティがreadonlyになる。

type MappedTypes = {
  readonly [P in 'tomato' | 'pumpkin']: string
}

スクリーンショット 2022-12-05 22.24.20.png

?をつければオプショナルになる。

type MappedTypes = {
  readonly [P in 'tomato' | 'pumpkin']?: string
}

スクリーンショット 2022-12-05 22.25.45.png

Conditional Types

Conditional Typesとは型のif文。
extendsの左側が右側に代入できるかどうかの条件分。

tomatostringに代入可能だったら、number型、そうでなければboolean型を返す。

type ConditionalTypes = 'tomato' extends string ?  number : boolean;

スクリーンショット 2022-12-05 22.35.49.png

Discussion