関数のGenerics
TypeScript
関数Genericsの構文
以下のように記述する。
function foo<T>(arg: T) {
return { value: arg };
}
関数Genericsの使い方
以下のように記述する。
function foo<T>(arg: T) {
return { value: arg };
}
const foo1 = foo<number[]>([1, 2]);
アロー関数での記述方法
以下のように引数の()の前にGenericsを記述する。
const foo = <T>(arg: T) => {
return { value: arg };
}
const foo1 = foo<number[]>([1, 2]);
暗黙的な型解決について
関数のGenericsは暗黙的に型が解決される仕組みがある。 以下のように<string>のようにしなくても引数の型を推論して、暗黙的に型を決定してくれる。
const foo = <T>(arg: T) => {
return { value: arg };
}
const foo1 = foo("");
// value: string;
const foo2 = foo(1);
// value: number;
const foo3 = foo(false);
// value: number;
このおかげで関数でGenericsを使用したとしても、使う側が毎回型指定をしなくて良くなるので、 非常に便利な機能。
どういう時に型引数を使うのか
Nullableかもしれない場合
Nullableかもしれない場合に以下のように型引数にnullを入れて対応する。 ※Nullable:Nullになりうる値
const foo = <T>(arg: T) => {
return { value: arg };
}
const foo1 = foo<string | null>("");
// value: string | null;
Nullableに限らずUnion Types等で後から引数に何が入ってくるかわからない場合に、 型引数を使用することが多い。
関数Genericsのextendsによる型制約
関数Genericsの型引数に制約を加えたい時にextendsによる型制約を用いる。
const foo = <T extends string>(arg: T) => {
return { value: arg };
}
const foo1 = foo<string>("");
const foo2 = foo(1); // 型 'number' の引数を型 'string' のパラメーターに割り当てることはできません。
関数Genericsのextendsによる型制約は重要なもので、しっかりと理解しておく。
なぜ関数Genericsのextendsによる型制約は重要なのか
argはある程度型を特定させるために必要で、もしextendsによる型制約が無かったら、 argが中でどのような型になっているかが分からなくなってしまう。
const foo = <T>(arg: T) => {
// argが中でどのような型になっているかが分からない
return { value: arg };
}
そのため、型制約がないとunknownとして扱われてしまい、メソッド等を呼び出せなくなってしまう。
const foo = <T>(arg: T) => {
arg.
// メソッドにアクセス出来ない
return { value: arg };
}
型制約があると
const foo = <T extends string>(arg: T) => {
arg.toUpperCase()
// メソッドにアクセス出来る
return { value: arg };
}
関数Genericsのextendsによる型の絞り込み
Union Typesでstringまたはnumber等の場合、以下のように上手く扱えなくなるケースが出てくる。
const foo = <T extends string | number>(arg: T) => {
arg.toUpperCase
// プロパティ 'toUpperCase' は型 'string | number' に存在しません。
// プロパティ 'toUpperCase' は型 'number' に存在しません。
return { value: arg };
}
そういった時に型の絞り込みを行い対応する。
const foo = <T extends string | number>(arg: T) => {
if(typeof arg === "string") {
return { value: arg.toUpperCase() };
}
return { value: arg.toFixed() };
}
型引数がUnion Typesの場合でも型の絞り込みを使うことによって、 関数内でうまく扱える。
ネストされたオブジェクトになっているなど、複雑なUnion Typesの場合でも ユーザー定義のType Guardを使うことによって型解決できる。