TypeScriptでジェネリックスを使用する方法
序章
動的で再利用可能なコードを作成する場合は、Do n't-Repeat-Yourself( DRY )の原則に従うことが重要です。 ジェネリックスを使用すると、TypeScriptコードでこれを実現するのに役立ちます。
ジェネリックを使用すると、動的で再利用可能なジェネリックコードブロックを記述できます。 さらに、TypeScriptのジェネリックスをクラス、インターフェイス、および関数に適用できます。
この記事では、ジェネリックスをTypeScriptコードに統合し、それらを関数とクラスに適用します。 また、インターフェイスを使用してTypeScriptでジェネリックスに制約を追加する方法についても学習します。
前提条件
このチュートリアルを正常に完了するには、次のものが必要です。
- マシンにインストールされているTypeScriptの最新バージョン。 この新しいTypeScriptプロジェクトを設定する方法チュートリアルは、これを達成するのに役立ちます。
ts-nodeの最新バージョンがインストールされています。 これは、TypeScriptコードをテストして実行する場合に必要です。 このTypeScriptスクリプトをts-nodeで簡単に実行するチュートリアルは、始めるのに最適な場所です。- TypeScriptでの関数とクラスの記述に精通していること。
ステップ1—ジェネリックを理解する
場合によっては、異なるデータ型に対して同じコードブロックを繰り返したいことがあります。 2つの異なるデータ型に使用されている同じ関数の例を次に示します。
// for number type
function fun(args: number): number {
return args;
}
// for string type
function fun(args: string): string {
return args;
}
この例では、numberタイプとstringタイプで同じ機能が繰り返されていることに注意してください。 ジェネリックは、上記の例のように同じコードブロックを記述して繰り返す代わりに、一般化されたメソッドを記述するのに役立ちます。
anyと呼ばれるタイプがあり、これを使用して、コードのジェネリックと同じ効果を実現できます。 anyタイプを使用すると、タイプチェックをオプトアウトできます。 ただし、anyはtype-safeではありません。 これは、anyを使用すると例外が発生する可能性があることを意味します。
これを実際に確認するには、anyタイプを前のコード例に適用します。
function fun(args: any): any {
return args;
}
numberおよびstringタイプをanyタイプに交換すると、関数が汎用になります。 ただし、落とし穴があります。anyタイプを使用するということは、fun関数が任意のデータを受け入れることができることを意味します。 その結果、型安全性も失われます。
anyタイプを使用することは、TypeScriptコードをより一般的にする方法ですが、常に最良のオプションであるとは限りません。 次のステップでは、タイプセーフなジェネリックを作成するための別のオプションを検討します。
ステップ2—タイプセーフジェネリックの作成
タイプセーフなジェネリックを作成するには、Typeパラメーターを使用する必要があります。 Typeパラメーターは、Tまたは<T>によって定義されます。 これらは、クラス、インターフェイス、および関数に渡されるパラメーターのデータ型を示します。
fun関数に戻り、Tを使用して、総称関数をタイプセーフにします。
index.ts
function fun<T>(args:T):T {
return args;
}
その結果、funはタイプセーフな総称関数になりました。 このタイプセーフなジェネリック関数をテストするには、resultという名前の変数を作成し、stringタイプパラメーターを使用してfunと等しくなるように設定します。 引数はHello World文字列になります。
index.ts
let result = fun<string>("Hello World");
fun機能をnumberタイプで使用してみてください。 引数を200に等しく設定します。
index.ts
let result2 = fun<number>(200);
このコードの結果を確認したい場合は、console.logステートメントを含めて、resultおよびresult2をコンソールに出力できます。
index.ts
console.log(result); console.log(result2);
最終的に、コードは次のようになります。
index.ts
function fun<T>(args:T):T {
return args;
}
let result = fun<string>("Hello World");
let result2 = fun<number>(200);
console.log(result);
console.log(result2);
ts-nodeを使用して、コンソールでこのTypeScriptコードを実行します。
npx ts-node index.ts
コードはエラーなしでレンダリングされます。 次の出力が表示されます。
OutputHello World 200
1つのパラメーターを持つ関数のタイプセーフなジェネリックを作成できるようになりました。 また、多くの異なるタイプの複数のパラメーターを持つ関数のジェネリックを作成する方法を知ることも重要です。
ステップ3—多くのタイプのパラメーターでジェネリックを使用する
関数に多くのパラメーターがある場合は、異なる文字を使用してタイプを示すことができます。 Tだけを使用する必要はありません。
params.ts
function fun<T, U, V>(args1:T, args2: U, args3: V): V {
return args3;
}
この関数は、args1、args2、およびarg3の3つのパラメーターを受け取り、args3を返します。 これらのパラメータは特定のタイプに制限されていません。 これは、T、U、およびVが、fun関数のパラメーターの総称型として使用されるためです。
result3という変数を作成し、funに割り当てます。 <string, number, boolean>タイプを含めて、T、U、およびV汎用タイプに入力します。 引数には、括弧内に保持された文字列、数値、およびブール値を含めます。
params.ts
let result3 = fun<string, number, boolean>('hey', 3, false);
これにより、3番目の引数falseが返されます。 これを確認するには、console.logステートメントを使用できます。
params.ts
console.log(result3);
ts-nodeコマンドを実行して、console.logステートメントの出力を確認します。
npx ts-node params.ts
これが出力になります:
Outputfalse
これで、複数のパラメーターを持つ関数の汎用型を作成できます。 関数と同様に、ジェネリックはclassesおよびinterfacesでも使用できます。
ステップ4—汎用クラスの作成
総称関数のように、クラスも総称にすることができます。 関数と同様に、角度(<>)括弧内のtypeパラメーターが使用されます。 次に、<T>タイプが、メソッドとプロパティを定義するためにクラス全体で使用されます。
numberとstringの両方の入力を受け取るクラスを作成し、それらの入力を使用して配列を作成します。 総称型パラメーターとして<T>を使用します。
classes.ts
class customArray<T> {
private arr: T[] = [];
}
これで、さまざまなタイプのアイテムを取り込む配列が配置されました。 customArray配列を返すgetItemsというメソッドを作成します。
classes.ts
getItems (arr: T[]) {
return this.arr = arr;
}
customArray配列の最後に新しいアイテムを追加するaddItemというメソッドを作成します。
classes.ts
addItem(item:T) {
this.arr.push(item);
}
arr: T[]引数は、配列内の項目が任意のタイプであることを意味します。 したがって、customArrayは、数値、ブール値、または文字列の配列にすることができます。
customArrayから指定されたアイテムを削除するremoveItemというメソッドを追加します。
classes.ts
removeItem(item: T) {
let index = this.arr.indexOf(item);
if(index > -1)
this.arr.splice(index, 1);
}
addItemメソッドと同様に、removeItemは任意のタイプのパラメーターを受け取り、指定されたパラメーターをcustomArray配列から削除します。
これで、汎用クラスcustomArrayが完成しました。 numberおよびstringタイプのcustomArrayのインスタンスを作成します。
numberタイプのcustomArrayのインスタンスに等しいnumObjセットと呼ばれる変数を宣言します。
classes.ts
let numObj = new customArray<number>();
addItemメソッドを使用して、番号10をnumObjに追加します。
classes.ts
numObj.addItem(10);
customArrayは汎用であるため、文字列の配列を作成するためにも使用できます。 文字列タイプのcustomArrayのインスタンスと等しいstrObjという変数を作成します。
classes.ts
let strObj = new customArray<string>();
addItemメソッドを使用して、文字列RobinをstrObj配列に追加します。
classes.ts
strObj.addItem(“Robin”);
コードの結果を確認するには、numObjとstrObjの両方に対してconsole.logステートメントを作成します。
classes.ts
console.log(numObj); console.log(strObj);
最終的に、コードは次のようになります。
classes.ts
class customArray<T> {
private arr: T[] = [];
getItems(arr: T[]) {
return this.arr = arr;
}
addItem(item:T) {
this.arr.push(item);
}
removeItem(item: T) {
let index = this.arr.indexOf(item);
if(index > -1)
this.arr.splice(index, 1);
}
}
let numObj = new customArray<number>();
numObj.addItem(10);
let strObj = new customArray<string>();
strObj.addItem(“Robin”);
console.log(numObj);
console.log(strObj);
ts-nodeを実行した後、次の出力が表示されます。
OutputcustomArray { arr: [ 10 ] }
customArray { arr: [ 'Robin' ] }
numberタイプとstringタイプの両方にcustomArrayクラスを使用しました。 ジェネリック型を使用することでこれを達成することができました。 ただし、ジェネリックスの使用にはいくつかの制約があります。 これについては、次のステップで説明します。
ステップ5—一般的な制約を理解する
これまで、ジェネリックスを使用して関数とクラスを作成してきました。 ただし、ジェネリックを使用することには欠点があります。 この欠点を実際に確認するには、関数の引数のlengthを返すgetLengthという関数を記述します。
制約.ts
function getLength<T>(args: T) : number {
return args.length;
}
この関数は、渡す型にlengthプロパティがある限り機能しますが、lengthプロパティを持たないデータ型は例外をスローします。
この問題には、一般的な制約を作成するという解決策があります。 これを行うには、最初にfuncArgsというインターフェイスを作成し、lengthプロパティを定義する必要があります。
制約.ts
interface funcArgs {
length: number;
}
ここで、getLength関数とextendを変更して、funcArgsインターフェースを制約として含めます。
制約.ts
function getLength<T extends funcArgs>(args:T) : number {
return args.length;
}
インターフェイスを使用して一般的な制約を作成しました。 さらに、このインターフェースでgetLength機能も拡張しました。 必須パラメータとしてlengthが必要になりました。 長さパラメーターを持たない引数を使用してこのgetLength関数にアクセスすると、例外メッセージが表示されます。
これが実際に動作することを確認するには、result4という変数を宣言し、3を引数としてgetLengthに割り当てます。
制約.ts
let result4 = getLength(3);
lengthパラメータの値が含まれていないため、これはエラーを返します。
Output⨯ Unable to compile TypeScript: index.ts:53:25 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'funcArgs'. 53 let result4 = getLength(3);
getLength関数を呼び出すには、[X51X]引数とともにlength引数を含める必要があります。
制約.ts
let result = getLength({ length: 5, name: 'Hello'});
これは、関数を呼び出す正しい方法です。 この呼び出しにはlengthプロパティがあり、関数は正常に機能します。 エラーメッセージは表示されません。
結論
このチュートリアルでは、ジェネリックスをTypeScript関数とクラスに正常に統合しました。 ジェネリックの制約も含めました。
次のステップとして、ReactでTypeScriptを使用する方法を学ぶことに興味があるかもしれません。 VS CodeでTypeScriptを操作する方法を学びたい場合は、この VisualStudioCodeでTypeScriptを操作する方法の記事から始めるのが最適です。