課題6: ジェネリクス (Generics) (所要時間目安: 20‐30分) - hideki5123/myts GitHub Wiki
次は、TypeScriptの強力な機能の一つであるジェネリクスです。ジェネリクスを使うことで、特定の型に縛られず、様々な型で再利用可能なコンポーネント(関数やクラスなど)を作成できます。C#にもジェネリクスがあるので、概念自体は非常に馴染み深いと思います。
C#との比較:
- 概念: 型をパラメータ化して、コードの再利用性と型安全性を両立させるという基本的な考え方は、TypeScriptもC#も同じです。
-
構文:
-
TypeScript: 関数やクラス名の後に
<T>
のように型パラメータを定義します (例:function identity<T>(arg: T)
,class Box<T>
)。 -
C#: 同様に
<T>
を使います (例:T Identity<T>(T arg)
,class Box<T>
)。構文は非常によく似ています。
-
TypeScript: 関数やクラス名の後に
-
型推論: どちらの言語も、ジェネリック関数の呼び出し時に型引数を推論する機能を持っています。
-
TypeScript:
let output = identity("myString");
// T は string と推論される -
C#:
var output = Identity("myString");
// T は string と推論される
-
TypeScript:
-
制約 (Constraints): 型パラメータに特定の制約(例: 特定のインターフェースを実装している型のみ)を付けたい場合があります。
-
TypeScript:
extends
キーワードを使います (例:function logLength<T extends { length: number }>(arg: T)
)。 -
C#:
where
キーワードを使います (例:void LogLength<T>(T arg) where T : ILengthy
)。目的は同じですが、構文が異なります。(今回は制約なしの基本的な使い方から始めます)
-
TypeScript:
課題内容:
-
新しいファイルの作成:
src
ディレクトリにgenerics.ts
という新しいファイルを作成してください。 -
ジェネリック関数とクラスの定義・使用:
generics.ts
に以下のようなコードを書いてみてください。// src/generics.ts export {}; // モジュール化のための空エクスポート // --- 1. ジェネリック関数 (Identity Function) --- // 型 T を受け取り、同じ型 T の値を返す関数 function identity<T>(arg: T): T { return arg; } // 呼び出し (型推論) let output1 = identity("myString"); // T は string と推論される let output2 = identity(100); // T は number と推論される let output3 = identity(true); // T は boolean と推論される // 呼び出し (型引数を明示) let output4 = identity<string>("explicit string"); let output5 = identity<number>(200); console.log("Identity Function Outputs:"); console.log(output1, typeof output1); console.log(output2, typeof output2); console.log(output3, typeof output3); console.log(output4, typeof output4); console.log(output5, typeof output5); // --- 2. 配列を扱うジェネリック関数 --- function logArrayFirstElement<T>(arr: T[]): void { if (arr.length > 0) { console.log(`First element: ${arr[0]}`); } else { console.log("Array is empty."); } } console.log("\nLogging Array First Elements:"); logArrayFirstElement([1, 2, 3]); // T は number と推論される logArrayFirstElement(["a", "b", "c"]); // T は string と推論される logArrayFirstElement([{ name: "Alice" }, { name: "Bob" }]); // T は { name: string } と推論される logArrayFirstElement<boolean>([true, false]); // 型引数を明示 logArrayFirstElement([]); // 空配列もOK // --- 3. ジェネリッククラス --- class Box<T> { private content: T; constructor(initialContent: T) { this.content = initialContent; } getContent(): T { return this.content; } setContent(newContent: T): void { this.content = newContent; } } // ジェネリッククラスのインスタンス化 let stringBox = new Box<string>("Hello Generics"); let numberBox = new Box<number>(123); let booleanBox = new Box(true); // 型推論も可能 (コンストラクタ引数から) console.log("\nGeneric Class Outputs:"); console.log(`stringBox content: ${stringBox.getContent()}`); stringBox.setContent("Updated string"); console.log(`stringBox updated content: ${stringBox.getContent()}`); console.log(`numberBox content: ${numberBox.getContent()}`); numberBox.setContent(456); console.log(`numberBox updated content: ${numberBox.getContent()}`); console.log(`booleanBox content: ${booleanBox.getContent()}`); // 推論された型は boolean // booleanBox.setContent(123); // Error: Argument of type 'number' is not assignable to parameter of type 'boolean'. // --- 4. ジェネリックインターフェース (参考) --- interface Pair<K, V> { key: K; value: V; } let kvp1: Pair<number, string> = { key: 1, value: "One" }; let kvp2: Pair<string, boolean> = { key: "active", value: true }; console.log("\nGeneric Interface Examples:"); console.log(kvp1); console.log(kvp2);
-
コンパイルと実行:
-
npx tsc
でコンパイルし、node dist/generics.js
で実行して結果を確認してください。 -
booleanBox.setContent(123);
のような型エラーになる行のコメントを外して、型安全性が保たれていることを確認してください。
-
-
コミットとPush:
-
src/generics.ts
の変更をコミットし (例:git commit -m "Add basic generics examples for functions and classes"
)、GitHubにPushしてください。 - 新しいコミットへのリンクを教えてください。
-
C#でのジェネリクスの経験があれば、TypeScriptのジェネリクスも比較的スムーズに理解できるはずです。構文の微妙な違いや、制約の書き方(今回は触れませんが)に注目してみてください。