序章

動的で再利用可能なコードを作成する場合は、Do n’t-Repeat-Yourself( DRY )の原則に従うことが重要です。 ジェネリックスを使用すると、TypeScriptコードでこれを実現するのに役立ちます。

ジェネリックを使用すると、動的で再利用可能なジェネリックコードブロックを記述できます。 さらに、TypeScriptのジェネリックスをクラス、インターフェイス、および関数に適用できます。

この記事では、ジェネリックスを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タイプを使用すると、タイプチェックをオプトアウトできます。 ただし、anytype-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コードを実行します。

  1. npx ts-node index.ts

コードはエラーなしでレンダリングされます。 次の出力が表示されます。

Output
Hello World 200

1つのパラメーターを持つ関数のタイプセーフなジェネリックを作成できるようになりました。 また、多くの異なるタイプの複数のパラメーターを持つ関数のジェネリックを作成する方法を知ることも重要です。

ステップ3—多くのタイプのパラメーターでジェネリックを使用する

関数に多くのパラメーターがある場合は、異なる文字を使用してタイプを示すことができます。 Tだけを使用する必要はありません。

params.ts
function fun<T, U, V>(args1:T, args2: U, args3: V): V {
  return args3;
}

この関数は、args1args2、およびarg3の3つのパラメーターを受け取り、args3を返します。 これらのパラメータは特定のタイプに制限されていません。 これは、TU、およびVが、fun関数のパラメーターのジェネリック型として使用されるためです。

result3という変数を作成し、funに割り当てます。 <string, number, boolean>タイプを含めて、TU、および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ステートメントの出力を確認します。

  1. npx ts-node params.ts

これが出力になります:

Output
false

これで、複数のパラメーターを持つ関数のジェネリック型を作成できます。 関数と同様に、ジェネリックはclassesおよびinterfacesでも使用できます。

ステップ4—ジェネリッククラスの作成

ジェネリック関数と同様に、クラスもジェネリックにすることができます。 関数と同様に、角度(<>)括弧内のtypeパラメーターが使用されます。 次に、<T>タイプが、メソッドとプロパティを定義するためにクラス全体で使用されます。

numberstringの両方の入力を受け取るクラスを作成し、それらの入力を使用して配列を作成します。 ジェネリック型パラメーターとして<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メソッドを使用して、番号10numObjに追加します。

classes.ts
numObj.addItem(10);

customArrayはジェネリックであるため、文字列の配列を作成するためにも使用できます。 文字列型のcustomArrayのインスタンスと等しいstrObjという変数を作成します。

classes.ts
let strObj = new customArray<string>();

addItemメソッドを使用して、文字列RobinstrObj配列に追加します。

classes.ts
strObj.addItem(“Robin”);

コードの結果を確認するには、numObjstrObjの両方に対して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を実行した後、次の出力が表示されます。

Output
customArray { 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]