ジェネリック型
ジェネリクス(Generics)
ジェネリクスの役立つ主な理由は、メンバ間で意味のある型制約を提供することです。メンバには以下のものがあります:
クラスのインスタンスメンバ
クラスメソッド
関数の引数
関数の戻り値
ジェネリクスのモチベーションとサンプル
単純なQueue(先入れ先出し)データ構造の実装を考えてみましょう。TypeScript/JavaScriptの単純なものは以下のようになります:
class Queue {
private data = [];
push(item) { this.data.push(item); }
pop() { return this.data.shift(); }
}この実装での1つの問題は、キューに何でも追加できることです。また、キューから要素を取り出すと、何が出てくるかわかりません。これを以下に示します。ここでは誰かがstringをキューにプッシュしていますが、実際にはnumbersだけがプッシュされることを想定しています。
class Queue {
private data = [];
push(item) { this.data.push(item); }
pop() { return this.data.shift(); }
}
const queue = new Queue();
queue.push(0);
queue.push("1"); // Oops a mistake
// a developer walks into a bar
console.log(queue.pop().toPrecision(1));
console.log(queue.pop().toPrecision(1)); // RUNTIME ERROR1つの解決策(実際にはジェネリクスをサポートしていない言語での唯一の解決策)は、これらの制約のために特別なクラスを作成することです。例えば素早くダーティに数値型のキューを作ります:
もちろん、これはすぐに苦痛になる可能性があります。文字列キューが必要な場合は、そのすべての作業をもう一度行う必要があります。あなたが本当に必要とすることは、型が何であれ、プッシュされているものの型とポップされたものの型は同じでなければならないということです。これは、ジェネリクスパラメータ(この場合はクラスレベル)で簡単に行えます:
すでに見たもう1つの例は、_reverse_関数の例です。ここでは、関数に渡されるものと関数が返すものの間の制約があります。
このセクションでは、クラスレベルと関数レベルで定義されているジェネリクスの例を見てきました。多少付け加えたいことは、メンバ関数のためだけにジェネリクスを作成できるということです。おもちゃの例として、reverse関数をUtilityクラスに移したところで、次のことを考えてみましょう:
ヒント:必要に応じてジェネリクスパラメータを呼び出すことができます。単純なジェネリックを使うときは
T、U、Vを使うのが普通です。複数のジェネリクス引数がある場合は、意味のある名前を使用してください。例えばTKeyとTValueです(一般にTを接頭辞として使用する規約は、他の言語(例えばC++)では_テンプレート_と呼ばれることもあります)。
デザインパターン:便利なジェネリック
次の関数を考えてみましょう。
この場合、タイプTは1つの場所でのみ使用されていることがわかります。したがって、メンバ間に制約はありません。これは、型安全性における型アサーションに相当します。
1回だけ使用されるジェネリクスは、型安全性に関してはアサーションよりも劣っています。それは、既に述べたように、あなたのAPIに利便性を提供するものです。
より明らかな例は、jsonレスポンスをロードする関数です。それは、あなたが渡した任意の型のPromiseを返します:
あなたは依然としてアノテーションを明示しなければならないことに注意してください。しかし、getJSON<T>のシグネチャ(config) => Promise<T>は、キータイプを減らすことができます(loadUsersの戻り値の型は、TypeScriptが推論可能なのでアノテーションする必要はありません):
戻り値としてのPromise<T>は、Promise<any>よりも断然優れています。
次の例では、ジェネリクスが引数のみに使用されています。
このようにすると、次のように、引数にマッチさせたい型をアノテートするのにジェネリクスTを利用できます。
最終更新
役に立ちましたか?