😺

Typescript(ジェネリック)

2023/03/02に公開

ここでは、ジェネリックについて知識を深めていきます!

Typescriptでは、ジェネリックを使って複数の型に対して汎用的な処理を行うことができます。ジェネリックを使うことで、コードの再利用性が高まり、型安全性も確保できます。本記事では、ジェネリックについての基本的な概念から、実際のコードでの使い方までを解説します。

ジェネリックとは何か?

ジェネリックとは、型をパラメータとして扱う仕組みのことです。例えば、以下のような関数があったとします。

function identity(arg: any): any {
  return arg;
}

この関数は、引数として渡された値をそのまま返すだけのものです。しかし、この関数の型情報は完全に失われています。引数としてnumberを渡しても、stringを渡しても、戻り値の型も全くわかりません。

ここで、ジェネリックを使って型をパラメータ化することで、関数をより汎用的にすることができます。

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

Tは、どんな型でも入ることができる型パラメータです。Tは関数の引数と戻り値の型を共通化するために使用されます。この関数を呼び出す時に、Tの具体的な型を指定することで、型安全なコードを書くことができます。

let output1 = identity<string>("hello");
let output2 = identity<number>(123);

output1とoutput2の型はそれぞれstringとnumberになります。これにより、引数と戻り値の型が一致しない場合にはコンパイルエラーが発生するため、型安全性が確保できます。

ジェネリックを使ったクラス

ジェネリックは、クラスの定義でも使用することができます。以下は、ジェネリックを使ったスタックの実装例です。

class Stack<T> {
  private items: T[] = [];

  push(item: T) {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
console.log(numberStack.pop()); // 3
console.log(numberStack.peek()); // 2
console.log(numberStack.isEmpty()); // false

const stringStack
stringStack.push("hello");
stringStack.push("world");
console.log(stringStack.pop()); // "world"
console.log(stringStack.peek()); // "hello"
console.log(stringStack.isEmpty()); // false

このように、Stackクラスはジェネリックを使って、任意の型のスタックを実現しています。

ジェネリック制約

ジェネリックを使用する場合、どんな型でも受け入れてしまうと、不正な型が引数に渡された場合にコンパイルエラーにならない可能性があります。そのため、ジェネリックに制約を設けることができます。

以下の例では、TPerson型またはそのサブクラスであることを要求しています。

class Employee {
  name: string;
  id: number;
  constructor(name: string, id: number) {
    this.name = name;
    this.id = id;
  }
}

class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

function work<T extends Person>(person: T): void {
  console.log(`${person.name} is working...`);
}

const employee = new Employee("John", 123);
const person = new Person("Bob");

work(employee); // OK
work(person); // OK
work("invalid input"); // コンパイルエラー

work関数の引数には、Person型またはそのサブクラスである必要があります。この制約をextendsキーワードで指定しています。

まとめ

ジェネリックを使うことで、汎用的なコードを書くことができます。ジェネリックを使用することで、コードの再利用性が高まり、型安全性も確保できます。ジェネリックは、関数やクラスの引数や戻り値の型を汎用化するために使用されます。また、ジェネリックに制約を設けることで、不正な型が引数に渡された場合にコンパイルエラーにすることができます。

Discussion