ジェネリックの概要
「TypeScript を使用して JavaScript アプリケーションをビルドする」ラーニング パスのこれまでのモジュールで、型の注釈をインターフェイス、関数、クラスに適用して、厳密に型指定されたコンポーネントを作成する方法について学習しました。 しかし、1 つだけではなく、さまざまな型で機能するコンポーネントを作成する場合はどうなるでしょうか? any
型を使用できますが、TypeScript 型チェック システムの機能を使用できなくなります。
ジェネリックは、コードベースで定義して再利用できるコード テンプレートです。 これを使用すると、関数、クラス、またはインターフェイスに対して、それらを呼び出す際に使用する型を指示することができます。 これは、引数を関数に渡すことと同じようなものですが、ジェネリックを使用すると、呼び出されたときにどの型を想定する必要があるかをコンポーネントに指示できます。
コードが次のような関数またはクラスである場合、ジェネリック関数を作成します。
- さまざまなデータ型を使用する。
- そのデータ型を複数の場所で使用する。
ジェネリックには、次の利点があります。
- 型を使用する場合の柔軟性が向上します。
- コードを再利用できます。
any
型を使用する必要性が低減します。
ジェネリックを使用する理由
ジェネリックを使用する理由を理解しやすくするために、例を見てみることにしましょう。
次の getArray
関数は、any
型の項目の配列を生成します。
function getArray(items : any[]) : any[] {
return new Array().concat(items);
}
次に、getArray
関数を呼び出して numberArray
変数を宣言し、それに数字の配列を渡し、文字列の配列を使用して stringArray
変数を宣言します。 ただし、any
型が使用されているため、コードで string
を numberArray
に、または number
を stringArray
にプッシュすることを妨げるものは何もありません。
let numberArray = getArray([5, 10, 15, 20]);
let stringArray = getArray(['Cats', 'Dogs', 'Birds']);
numberArray.push(25); // OK
stringArray.push('Rabbits'); // OK
numberArray.push('This is not a number'); // OK
stringArray.push(30); // OK
console.log(numberArray); // [5, 10, 15, 20, 25, "This is not a number"]
console.log(stringArray); // ["Cats", "Dogs", "Birds", "Rabbits", 30]
では、関数を呼び出したときに配列に格納される値の型を決定し、TypeScript に渡された値を型チェックして、その型であることを確認する必要がある場合はどうなるでしょうか? ここで役立つのがジェネリックです。
次の例では、ジェネリックを使用して getArray
関数を書き直します。 これで、関数を呼び出したときに、指定した任意の型が受け入れられるようになりました。
function getArray<T>(items : T[]) : T[] {
return new Array<T>().concat(items);
}
ジェネリックでは、1 つ以上の型変数を山かっこ (< >
) で囲んで定義して、コンポーネントに渡す 1 つまたは複数の型を指定します (型変数は、型パラメーターまたはジェネリック パラメーターとも呼ばれます)。上記の例の関数で、型変数は <T>
という名前です。 T
は、ジェネリックに使用される一般的な名前ですが、好きなように名前を付けることができます。
型変数を指定した後、パラメーター内の型または戻り値の型の代わりに、または型の注釈を追加する関数内の任意の場所で、それを使用することができます。
型変数 T は、型の注釈が必要な場所ならどこでも使用できます。 getArray 関数では、items パラメーターの型および関数の戻り値の型を指定するために使用されます。また、項目の新しい配列を返すためにも使用されます。
関数を呼び出して、それに型を渡すには、関数名に <type>
を追加します。 たとえば、getArray<number>
は、number
値の配列のみを受け入れ、number
値の配列を返すように関数に指示します。 型は number
として指定されているため、TypeScript は、number
値を関数に渡すことを想定し、それ以外の場合はエラーが発生します。
Note
関数を呼び出すときに型変数を省略すると、TypeScript は型を推論します。 ただし、これは、単純なデータに対してのみ機能します。 配列またはオブジェクトで渡すと、any 型が推論され、型チェックは行われません。
次の例では、numberArray
および stringArray
の変数宣言を更新し、目的の型を指定して関数を呼び出すことにより、TypeScript で無効な項目が配列に追加されないようにします。
let numberArray = getArray<number>([5, 10, 15, 20]);
numberArray.push(25); // OK
numberArray.push('This is not a number'); // Generates a compile time type check error
let stringArray = getArray<string>(['Cats', 'Dogs', 'Birds']);
stringArray.push('Rabbits'); // OK
stringArray.push(30); // Generates a compile time type check error
複数の型変数の使用
ジェネリック コンポーネントで使用できる型変数の数は、1 つに制限されません。
たとえば、identity
関数は、value
と message
の 2 つのパラメーターを受け入れ、value
パラメーターを返します。 各パラメーターと戻り値の型に異なる型を割り当てるために、2 つのジェネリック T
と U
を使用することができます。 変数 returnNumber
は、value
および message
引数の型として <number, string>
を使用する identity
関数を呼び出すことによって初期化され、returnString
は、<string, string>
を使用して呼び出すことによって初期化され、returnBoolean
は、<boolean, string>
を使用して呼び出すことによって初期化されます。 これらの変数を使用する場合、TypeScript は、値の型チェックを行い、競合が発生する場合はコンパイル時エラーを返します。
function identity<T, U> (value: T, message: U) : T {
console.log(message);
return value
}
let returnNumber = identity<number, string>(100, 'Hello!');
let returnString = identity<string, string>('100', 'Hola!');
let returnBoolean = identity<boolean, string>(true, 'Bonjour!');
returnNumber = returnNumber * 100; // OK
returnString = returnString * 100; // Error: Type 'number' not assignable to type 'string'
returnBoolean = returnBoolean * 100; // Error: Type 'number' not assignable to type 'boolean'