3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScriptAdvent Calendar 2024

Day 22

TypeScriptのジェネリクスを覚えた

Last updated at Posted at 2024-12-22

これは何

日常的にTypeScript/Reactでコードを書いているものの、主にUIの作り込みが多いのでTypeScriptの知識としてはそこまで深く知らなくても仕事がこなせてました。
そこで、ちゃんとTypeScriptを書けるようになりたいと思い改めて知らなかったことを書いた記事になります。

ジェネリクスを知ったきっかけ

type-challengesが流行っているので、試しに挑むも1問も解けず、改めてサバイバルTypeScriptから勉強し直したのが、ジェネリクス型を知ったきっかけでもあります。

ジェネリクスとはどういう意味?

ジェネリクス(generics)とは、プログラミング言語の機能・仕様の一つで、同じコードで様々な異なるデータ型のデータを処理できるようにする仕組み。C++言語などでは、ほぼ同様の機能を「テンプレート」(template)という。

IT用語辞典より

プログラミングにおいて、特定のデータ型に縛られることなく、さまざまなデータ型に対応できる柔軟なプログラムを作成することが出来るようになるようです。

実際の例

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

上記のコードは公式のコードからコピペしてきたものです。
引数の型がanyになっています。
これだと、あらゆる型を受け入れるという意味では汎用的ではあるけど、そもそもの型情報が何だったのかが分からなくなります。
そしてコンパイル時に型のチェックが行われないため、予期しない型が渡された場合にエラーが発生する可能性があります。

公式のコードを少し変更してみます。


const identity(arg: any): any {
  console.log(arg.toUpperCase()); //stringを想定している
}

console.log(identity("hello"));
console.log(identity(1)); //実行されるまでエラーは出ない

すると以下の様な実行結果が出てきます。

HELLO
error: Uncaught (in promise) TypeError: arg.toUpperCase is not a function

この場合、argが文字列であることを仮定して.toUpperCase()を呼び出していますが、数値を渡すとエラーになります。ジェネリクスを使えば、このような問題を防げます。
ジェネリクスを使用したコードが以下です。

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

const hello = identity<string>("hello").toUpperCase();
const one = identity<number>(1);

console.log(hello);
console.log(one);

実行結果が以下になります。

HELLO
1

型引数を省略することもできるようです。


const hello = identity("hello").toUpperCase(); // string
const one = identity(1); // number

ジェネリクスの型変数

さきほどのコードで書いた関数identityの後に記述した<Type>というのが型変数になります。
ここで名前をTypeと付けていますが型「変数」なので、ある程度自由に名前を付けることができます。
ただ慣習みたいなものがあり、大文字のTで書かれることが多いようです。

function identity<T>(arg: T): T { //型変数をTに変更
  return arg;
}

TypeScriptの慣習として、型変数名にはTを用いることが多いです。このTはtemplateの略と言われています。
単純なジェネリクスで、型変数が2つある場合は、TとUが用いられることがあり、その理由はアルファベット順でTの次がUだからです。この規則にしたがって、3つ目の型変数はVとする場合もあります。

型変数の慣習より

型引数に制約をつける

ジェネリクスの型引数を特定の型に限定することができます。

function identity<T extends string | boolean>(arg: T): T {
	return arg;
}

型変数Tの後にextendsと記述してジェネリクスの型Tを特定の型に限定することができます。
上記ではstring型とboolean型に限定しています。

では下記のコードはどうなったのでしょうか?

const hello = identity<string>("hello").toUpperCase();
const one = identity<number>(1);

エディタ上でちゃんとエラーが出ていることが分かります。

スクリーンショット 2024-12-23 0.03.50.png

extends以外でもimplementsというキーワードがあり、implementsはインターフェースでの限定に使われるようです。

interface ValueObject<T> {
  value: T;

  toString(): string;
}

class UserID implements ValueObject<number> {
  public value: number;

  public constructor(value: number) {
    this.value = value;
  }

  public toString(): string {
    return `${this.value}`;
  }
}

class Entity<ID extends ValueObject<unknown>> {
  private id: ID;

  public constructor(id: ID) {
    this.id = id;
  }

  //...
}

EntityクラスはValueObjectインターフェースを実装しているクラスをIDとして受ける構造になっていますが19行目にあるようにこのときの型引数の制約はimplementsではなくextendsでなければなりません。

型引数に制約をつけるより

あとextendsは一つしか継承できないけど、implementsは複数継承できるという違いがあるようです。

参考

3
0
0

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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?