ジェネリック型(Generics)の指定方法と利便性

TypeScriptのジェネリック型(Generics, 総称型)の指定方法について解説します。また、ジェネリック型を「利用しないケース」と「利用したケース」を比べて、どういったメリットがあるのか確認します。

ジェネリック型を利用しないケース

stringnumber を引数で受け取り、そのまま返す関数を例に考えます。

複数の関数を定義

2つ関数を作ります。

const testString = (x: string): string => x
const testNumber = (x: number): number => x

console.log(testString('Hello'))
console.log(testNumber(100))

関数の処理は同じなのに、複数の関数を定義しなければいけません。

anyを利用

any を利用して実装してみます。

下記コードはTypeScriptのコンパイルでエラーとなりません。

const test = (x: any): any => x

console.log(test('Hello').length)
console.log(test(100).length)

コンパイル後のソースを実行してみます。

$ node test1.js
5
undefined

undefined と表示されています。 test(100) のときの戻り値は number であり、string型の lengthプロパティ が存在しないためです。

このように any を利用した場合ですと、実行時にしか不具合に気づけません。

ジェネリック型を利用したケース

ジェネリック型を利用するには、以下のように記述します。

const test = <T>(x: T): T => x

console.log(test<string>('Hello').length)
console.log(test<number>(100).length)

コンパイルしてみます。

$ tsc test2.ts
test2.ts:4:31 - error TS2339: Property 'length' does not exist on type 'number'.

4 console.log(test<number>(100).length)
                                ~~~~~~


Found 1 error.

ジェネリック型を利用することで、 以下の利便性を得ることができました。

  • 1つの関数で定義できる
  • 不具合があれば、コンパイル時点でエラーを知らせてくれる

ジェネリック型の利用方法

関数で利用

function test<T>(x: T) {
    return x
}

アロー関数の場合、以下のように記述します。

const test = <T>(x: T): T => x

呼び出し|型を明示的に指定

呼び出し時に、<number> <string> のように型を明示的に指定して呼び出します。

const test = <T>(x: T): T => x

console.log(test<number>(100).length)
console.log(test<string>('Hello').length)
500-typescript-generics_use1.png

VSCodeの場合、赤波線でエラーを教えてくれます。

呼び出し|型推論

型指定を記述しなくても、引数の値だけを確認して自動で T が何になるのかを判定してくれます。

const test = <T>(x: T): T => x

console.log(test(100).length)
console.log(test('Hello').length)
500-typescript-generics_use2.png

VSCodeの場合、赤波線でエラーを教えてくれます。

複数のジェネリック型を利用

下記コードには不具合があります。

let echo1 = <T>(x: T, y: T): void => {
    console.log(x)
    console.log(y)
}
echo1(100, 'Hello')

1つのジェネリック型に、引数で複数の型(numberstring)を渡しているためコンパイルでエラーとなります。

$ tsc test1.ts
test1.ts:5:12 - error TS2345: Argument of type '"Hello"' is not assignable to parameter of type 'number'.

5 echo1(100, 'Hello')
             ~~~~~~~


Found 1 error.

複数のジェネリック型を利用したい場合には、以下のように記述します。

let echo2 = <T, U>(x: T, y: U): void => {
    console.log(x)
    console.log(y)
}
echo2(100, 'Hello')
500-typescript-generics_use3.png

VSCodeの場合、赤波線でエラーを教えてくれます。

クラスで利用

クラス名の後に <> を追加します。

class test<T> {
    xxx: T
}

let testNumber = new test<number>()
testNumber.xxx = 100

let testString = new test<string>()
testString.xxx = 'Hello'

参考