TypeScriptでジェネリックスを使用する方法
序章
動的で再利用可能なコードを作成する場合は、Do n’t-Repeat-Yourself( DRY )の原則に従うことが重要です。 ジェネリックスを使用すると、TypeScriptコードでこれを実現するのに役立ちます。
ジェネリックを使用すると、動的で再利用可能なジェネリックコードブロックを記述できます。 さらに、TypeScriptのジェネリックスをクラス、インターフェイス、および関数に適用できます。
この記事では、ジェネリックスをTypeScriptコードに統合し、それらを関数とクラスに適用します。 また、インターフェイスを使用してTypeScriptでジェネリックスに制約を追加する方法についても学習します。
前提条件
このチュートリアルを正常に完了するには、次のものが必要です。
- マシンにインストールされているTypeScriptの最新バージョン。 この新しいTypeScriptプロジェクトを設定する方法チュートリアルは、これを達成するのに役立ちます。
ts-node
の最新バージョンがインストールされています。 これは、TypeScriptコードをテストして実行する場合に必要です。 このts-nodeを使用した簡単なTypeScriptスクリプトの実行チュートリアルは、開始するのに最適な場所です。- 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
を使用して、ジェネリック関数をタイプセーフにします。
function fun<T>(args:T):T {
return args;
}
その結果、fun
はタイプセーフなジェネリック関数になりました。 このタイプセーフなジェネリック関数をテストするには、result
という名前の変数を作成し、string
タイプパラメーターを使用してfun
と等しくなるように設定します。 引数はHello World
文字列になります。
let result = fun<string>("Hello World");
fun
機能をnumber
タイプで使用してみてください。 引数を200
に等しく設定します。
let result2 = fun<number>(200);
このコードの結果を確認したい場合は、console.log
ステートメントを含めて、result
およびresult2
をコンソールに出力できます。
console.log(result);
console.log(result2);
最終的に、コードは次のようになります。
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
だけを使用する必要はありません。
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
ジェネリックタイプに入力します。 引数には、括弧内に保持された文字列、数値、およびブール値を含めます。
let result3 = fun<string, number, boolean>('hey', 3, false);
これにより、3番目の引数false
が返されます。 これを確認するには、console.log
ステートメントを使用できます。
console.log(result3);
ts-node
コマンドを実行して、console.log
ステートメントの出力を確認します。
- npx ts-node params.ts
これが出力になります:
Outputfalse
これで、複数のパラメーターを持つ関数のジェネリック型を作成できます。 関数と同様に、ジェネリックはclasses
およびinterfaces
でも使用できます。
ステップ4—ジェネリッククラスの作成
ジェネリック関数と同様に、クラスもジェネリックにすることができます。 関数と同様に、角度(<>
)括弧内のtype
パラメーターが使用されます。 次に、<T>
タイプが、メソッドとプロパティを定義するためにクラス全体で使用されます。
number
とstring
の両方の入力を受け取るクラスを作成し、それらの入力を使用して配列を作成します。 ジェネリック型パラメーターとして<T>
を使用します。
class customArray<T> {
private arr: T[] = [];
}
これで、さまざまなタイプのアイテムを取り込む配列が配置されました。 customArray
配列を返すgetItems
というメソッドを作成します。
getItems (arr: T[]) {
return this.arr = arr;
}
customArray
配列の最後に新しいアイテムを追加するaddItem
というメソッドを作成します。
addItem(item:T) {
this.arr.push(item);
}
arr: T[]
引数は、配列内の項目が任意のタイプであることを意味します。 したがって、customArray
は、数値、ブール値、または文字列の配列にすることができます。
customArray
から指定されたアイテムを削除するremoveItem
というメソッドを追加します。
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
セットと呼ばれる変数を宣言します。
let numObj = new customArray<number>();
addItem
メソッドを使用して、番号10
をnumObj
に追加します。
numObj.addItem(10);
customArray
はジェネリックであるため、文字列の配列を作成するためにも使用できます。 文字列型のcustomArray
のインスタンスと等しいstrObj
という変数を作成します。
let strObj = new customArray<string>();
addItem
メソッドを使用して、文字列Robin
をstrObj
配列に追加します。
strObj.addItem(“Robin”);
コードの結果を確認するには、numObj
とstrObj
の両方に対してconsole.log
ステートメントを作成します。
console.log(numObj);
console.log(strObj);
最終的に、コードは次のようになります。
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
という関数を記述します。
function getLength<T>(args: T) : number {
return args.length;
}
この関数は、渡す型にlength
プロパティがある限り機能しますが、length
プロパティを持たないデータ型は例外をスローします。
この問題には、一般的な制約を作成するという解決策があります。 これを行うには、最初にfuncArgs
というインターフェイスを作成し、length
プロパティを定義する必要があります。
interface funcArgs {
length: number;
}
ここで、getLength
関数とextend
を変更して、funcArgs
インターフェースを制約として含めます。
function getLength<T extends funcArgs>(args:T) : number {
return args.length;
}
インターフェイスを使用して一般的な制約を作成しました。 さらに、このインターフェースでgetLength
機能も拡張しました。 必須パラメータとしてlength
が必要になりました。 長さパラメーターを持たない引数を使用してこのgetLength
関数にアクセスすると、例外メッセージが表示されます。
これが実際に動作することを確認するには、result4
という変数を宣言し、3
を引数としてgetLength
に割り当てます。
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]