649
382

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【TypeScript】Generics(ジェネリックス)を理解する

Last updated at Posted at 2020-02-27

概要

TypeScriptにおけるGeneric(ジェネリック)とは何かサンプルコードと共に簡単にまとめました。

Genericsとは?

まず、C#Javaといった言語もGenericsを搭載しており、TypeScriptに限った機能ではありません。

Genericsは抽象的な型引数を使用して、実際に利用されるまで型が確定しないクラス関数インターフェイスを実現する為に使用されます。
これだけだとイメージが湧きづらいと思いますので、実際にサンプルコードを交えて理解していきます。

Genericsの簡単な具体例(関数編)

下記のように同じようなコードを別の型で繰り返す場合があるとします。

// number型
function test(arg: number): number {
  return arg;
}

// string型
function test2(arg: string): string {
  return arg;
}

test(1); //=> 1
test2("文字列"); //=> 文字列

これをGenericsを使用する事で下記のように書く事が可能です。
test<string>の引数はstring型だけ、またtest<number>の引数はnumber型だけが許されるようになります。

function test<T>(arg: T): T {
  return arg;
}

test<number>(1); //=> 1
test<string>("文字列"); //=> 文字列

//※ Genericsでも型推論ができるので、引数から型が明示的にわかる場合は省略が可能
test("文字列2"); //=> "文字列2"

つまり、上記では抽象的な型引数<T>を関数に与え、実際に利用されるまで型が確定しない関数を作成しています。

複数の型引数を定義する

複数の型引数を使用することも可能です。
型引数の名前に特に決まりはありませんが、慣習的にT, U等の大文字のアルファベットが使用される事が多いです。

function test<T, U, P>(arg1:T, arg2: U, arg3: P): P {
  return arg3;
}

//※ Genericsでも型推論ができるので、引数から型が明示的にわかる場合は省略が可能
test("文字列", true, 4); //=> 4

Genericsの簡単な具体例(クラス編)

Generic関数の様に型引数を渡す事で、クラスもジェネリック化する事が可能です。
型引数Tはメソッドの返り値の型や、引数の型として、クラスを通して使用されている事が見てとれます。

class Klass<T> {
  item: T;

  constructor(item: T) {
    this.item = item;
  }

  getItem(): T {
    return this.item;
  }
}

let strObj = new Klass<string>("文字列1");
strObj.getItem(); //=> "文字列1"

let numObj = new Klass<number>(5);
numObj.getItem(); //=> 5

Genericsの簡単な具体例(インターフェイス編)

こちらも上記のGeneric関数・クラスと同じ要領でGenericインターフェイスを作成する事が可能です。

interface KeyValue<T, U> {
    key: T;
    value: U;
}

let obj: KeyValue<string, number> = { key: "文字列", value: 2 } //= {key: "文字列", value: 2}

型引数に制約を付ける

ここまで紹介したGenericの型引数はどんな型の引数も受け入れてきました。
しかし、引数で受け入れる値を特定の型のみに制限したい場合もあります。

例えば下記の例ではargnameというプロパティを取得しようとしていますが、全ての型がnameを持つ訳ではないので、コンパイラが警告を出しています。

function getName<T>(arg: T): string {
  return arg.name; // Property 'name' does not exist on type 'T'.
}
// argの型はこの時点でnameを持つか不明なので、コンパイラは警告を出す。

その様な場合、下記の様に書くことで、Textendsで指定したインタフェースを満たす型でなければならないということを指定する事ができます。
これにより、実装時にコンパイラエラーも起こりません。
また、関数を呼び出す際に制約に違反する引数を渡した場合にはエラーを出してくれます。

interface argTypes {
  name: string;
}

function getName<T extends argTypes>(arg: T): string {
  return arg.name;
}

getName({ name: "鈴木一郎" });

参考

649
382
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
649
382

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?