TypeScript - ジェネリクス:入門ガイド

こんにちは、未来のプログラミングスーパースター!今日は、TypeScriptのジェネリクスの世界への興味深い旅を始めます。プログラミングが新しい方也不用担心——あなたの親切なガイドとして、私はここにいますので、少しずつ進んでいきましょう。このチュートリアルの終わりには、ジェネリクスをプロのように扱えるようになるでしょう!

TypeScript - Generics

ジェネリクスとは何か、そしてなぜ私たちは関心を持つべきか?

本格的な内容に入る前に、簡単な類似を始めましょう。あなたがどんなアイテムも保持できる魔法の箱を持っていると imagine してください。時には本を入れ、時にはおもちゃ、時にはサンドイッチに入れることもあります。TypeScriptにおけるジェネリクスも基本的に同じです——柔軟で再利用可能なコードを作成し、さまざまなデータ型で動作できるようにするものです。

問題の例

ジェネリクスが役に立ついくつかのシナリオを見てみましょう:

  1. どんなデータ型の配列(数値、文字列、オブジェクト)も逆転できる関数を作成したい。
  2. どんなデータ型のデータも保存して取得できるクラスが必要。
  3. さまざまなデータ型で動作するユーティリティ関数を構築中。

ジェネリクスなしでは、各データ型ごとに別々の関数やクラスを書かなくてはならず、それは多くの反復を意味します。そして、優れたプログラマーとして知っている通り、反復はクリーンなコードの敵です!

TypeScriptジェネリクスで救出!

では、ジェネリクスが実際にどのように動作するかを袖をまくって見てみましょう。

基本的なジェネリック関数

以下は、どんなデータ型でも動作する簡単なジェネリック関数です:

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

これを分解してみましょう:

  • <T> は私たちの型パラメータです。データ型のプレースホルダーのようなものです。
  • (arg: T) は、私たちの関数が型 T の引数を受け取ることを意味します。
  • : T は、私たちの関数が型 T の値を返すことを意味します。

この関数を使うとこんな感じです:

let output1 = identity<string>("Hello, Generics!");
let output2 = identity<number>(42);

console.log(output1); // "Hello, Generics!"
console.log(output2); // 42

クールですね?同じ関数が違うデータ型で動作します!

ジェネリックインターフェース

インターフェースでもジェネリクスを使えます。以下はその例です:

interface GenericBox<T> {
contents: T;
}

let stringBox: GenericBox<string> = { contents: "A secret message" };
let numberBox: GenericBox<number> = { contents: 123 };

console.log(stringBox.contents); // "A secret message"
console.log(numberBox.contents); // 123

私たちの GenericBox はどんなコンテンツでも保持できます。先ほど話した魔法の箱のようなものです!

ジェネリッククラス

簡単なデータストアとして動作するジェネリッククラスを作成してみましょう:

class DataStore<T> {
private data: T[] = [];

addItem(item: T): void {
this.data.push(item);
}

getItems(): T[] {
return this.data;
}
}

let stringStore = new DataStore<string>();
stringStore.addItem("Hello");
stringStore.addItem("World");
console.log(stringStore.getItems()); // ["Hello", "World"]

let numberStore = new DataStore<number>();
numberStore.addItem(1);
numberStore.addItem(2);
console.log(numberStore.getItems()); // [1, 2]

この DataStore クラスは、どんなデータ型のデータも保存して取得できます。便利ですね?

ジェネリック制約

時々、ジェネリクスで使用できるデータ型を制限したいことがあります。それには制約を使います:

interface Lengthy {
length: number;
}

function logLength<T extends Lengthy>(arg: T): void {
console.log(arg.length);
}

logLength("Hello"); // 5
logLength([1, 2, 3]); // 3
logLength({ length: 10 }); // 10
// logLength(123); // エラー: Numberにはlengthプロパティがありません

ここで、私たちの logLength 関数は、length プロパティを持つデータ型のみで動作します。

ジェネリクスの利点

ジェネリクスの動作を見てきましたので、その利点をまとめましょう:

  1. コードの再利用性:一度書いて、さまざまなデータ型で使用可能。
  2. 型の安全性:コンパイル時に型関連のエラーをキャッチ。
  3. 柔軟性:さまざまなデータ型で動作するコンポーネントを作成。
  4. 明確さ:入力と出力の関係を明確にします。

ジェネリックメソッド表

以下は、よく遭遇するであろういくつかの一般的なジェネリックメソッドの表です:

メソッド 説明
Array.map<U>() 配列要素を変換 [1, 2, 3].map<string>(n => n.toString())
Promise.all<T>() すべてのプロミスが解決するのを待つ Promise.all<number>([Promise.resolve(1), Promise.resolve(2)])
Object.keys<T>() オブジェクトのキーを配列として取得 Object.keys<{name: string}>({name: "Alice"})
JSON.parse<T>() JSON文字列をオブジェクトに解析 JSON.parse<{age: number}>('{"age": 30}')

結論

おめでとうございます!あなたはTypeScriptのジェネリクスの素晴らしい世界への第一歩を踏み出しました。ジェネリクスは最初は少し難しいように感じるかもしれませんが、練習を重ねれば自然と使いこなせるようになります。

あなたのプログラミングの旅を続ける中で、ジェネリクスはTypeScriptのツールボックスにおけるスイスアーミーナイフのように——多様で強力で非常に役立ちます。それでは、若いパダワン、コードを書き続け、ジェネリクスとともに進んでいきましょう!

ハッピーコーディング、次回までの間に、探索し続け、学び続けてください!

Credits: Image by storyset