概要
TypeScriptにおけるジェネリック型パラメーター(ジェネリック型)の基本について学んだことをまとめてみました。理解の一助となれば幸いです。
ジェネリック型とは?
TypeScriptを用いて関数filterを設計するとします。関数を設計するのですから、関数のパラメーターの型を定義してあげる必要があります。しかし、その関数に渡す引数は実際に呼び出すときまでわからない場合どうしましょうか?
// どうやって呼び出すかまだ決まってない。。
// 型アノテーションの指定〇〇はどうしよう。。。。
function filter(array: 〇〇): 〇〇 {
return array.reverse();
}
// 呼び出す際にどれで呼び出すのかがわからない。。。
filter(['a', 'b']); // これ?
filter([1, 2]); // これ?
filter([{'a': 1}, {'b': 2}]); // はたまたこれなのか?
その問題を解決してくれるのが、**ジェネリック型パラメーター(ジェネリック型)**です。これは、関数を呼び出すたびに引数から型を推論してくれるというものです。
上記の例をジェネリック型を用いて書き変えてみます。
function filter<T>(array: T[]): T[] {
return array.reverse();
}
// どれが呼ばれてもエラーなし!
filter(['a', 'b']); // Tは'string'
filter([1, 2]); // Tは'number'
filter([{'a': 1}, {'b': 2}]); // Tは'[{'a': number},{'b': number}]'
関数名の後に<T>
を記述し、パラメーターの型がT[]
になっています。そうすると、関数を呼び出す際、パラメーターarrayに渡す引数から型を推測し、Tにバインドされます。そして、すべてのTをその型で置き換えます。
関数を呼び出すたびに引数から型を推測し、Tに対して再バインドされます。
ここで、「T」は単に型名であり、別の任意の名前でも構いません。慣例的にTypeの頭文字Tを使用する傾向があるだけです。
ジェネリック型は、型エイリアス、クラス、インタフェースでも使用可能であり、使えるところでは、できるだけ使用する方が良いです。そうすることで、コードの汎用性、再利用性、簡潔性を保つことができます。
単一関数に2つのジェネリック型
以下のようにジェネリック型を2つにして関数を設計することも可能です。ジェネリック型は、カンマで区切って必要な分だけ宣言できます。
function filter<T, U>(array: T[], f: (item: T) => U): U[] {
let result = [];
for(let i = 0; i < array.length; i++) {
result[i] = f(array[i]);
}
return result;
}
// 第一引数からT(string)、第二引数からU(boolean)を予測
filter(['a', 'b'], item => {return item ==='a'});
また、ジェネリック型を明示的にアノテーションをすることもできます。しかし、基本は引数から推論できるものは推論させればよいと思います。ただし、推論できない場合もあります。詳しくは次章に記載します。
function filter<T, U>(array: T[], f: (item: T) => U): U[] {
let result = [];
for(let i = 0; i < array.length; i++) {
result[i] = f(array[i]);
}
return result;
}
// 明示的にT, Uの型をアノテーション
filter<string, boolean>(['a', 'b'], item => {return item ==='a'});
関数を呼びだすときに明示的型アノテーションする必要がある関数
引数よりジェネリック型を推論させるべきと述べましたが、以下の場合はどうでしょうか?
let promise = new Promise(resolve => {
resolve(30);
});
引数resoleve => {resoleve(30)}
からジェネック型を推論できるだろうか?できませんね。この状態で、以下のアクセスするとエラーがでます。
let promise = new Promise(resolve => {
resolve(30);
});
promise.then(result => {
let value = result + 100; // エラー:resultは'unknown'です
})
なので、明示的にジェネリック型をアノテーションしてあげる必要があります。
let promise = new Promise<number>(resolve => {
resolve(30);
});
promise.then(result =>
let value = result + 100; // resultは'number'
)
最後に
ジェネリック型パラメータについて簡単にまとめてみました。
間違っている個所があればコメントいただけると幸いです。
参考
Boris Cherny 著 今村謙士 監訳 「プログラミングTypeScript」 オライリー・ジャパン