こんにちは! 株式会社アルシエで教育に関するサポートをしている岸本です。
今回のテーマは「TypeScriptパート6」です。
Generics
Genericsは型の決定を遅延できるものです。
例えば以下のコードのvalueをnumber、booleanでも使いたいときにGenericsを使います。
type bar = {
value: string;
};
型を自由に定義することができるので、後から使い回しができます。
// Generics
type bar<T> = {
value: T;
};
const foo: bar<number> = {
value: 3,
};
const foo: bar<string> = {
value: "3",
};
const foo: bar<boolean> = {
value: true,
};
Genericsの初期値について
// = stringで初期値
type bar<T = string> = {
value: T;
};
const foo: bar<number> = {
value: 3,
};
// 初期値がstringだから<string>を書かなくていい
const foo: bar = {
value: "3", // fooはstring型
};
型の制約
// 型の制約でstring型を満たさないと無理
type bar<T extends string> = {
value: T;
};
const foo: bar<number> = {
value: 3, // numberとstring型の互換性がない
};
const foo: bar = {
value: "3",
}
初期値と合わせて使うこともできます。
type bar<T extends string | number = string> = {
value: T;
};
const foo: bar<number> = {
value: 3,
};
const foo: bar = {
value: "3",
}
関数のGenerics
// 関数のGenericsの書き方
function foo<T>(arg: T) {
return { value: arg };
}
// foo<number[]> ←ここで型が決まる
const foo1 = foo<number[]>([1, 2]);
// アロー関数
const foo = <T>(arg: T) => {
return { value: arg };
};
const foo1 = foo<number[]>([1, 2]);
暗黙的な型解決
関数のGenericsは暗黙的に型が解決される仕組みがあります。
以下のように<string>のようにしなくても引数の型を推論して、暗黙的に型を決定してくれます。
// 関数のGenericsの書き方
function 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 を入れて対応します。
const foo = <T>(arg: T) => {
return { value: arg };
};
const foo1 = foo<string | null>("");
// value: string | null;
関数 Generics の extendsによる型制約
const foo = <T extends string>(arg: T) => {
return { value: arg };
};
const foo1 = foo<string>("");
const foo2 = foo(1);// number の引数を型 string のパラメーターに割り当てることはできません
関数において、extendsの型制約は重要です。
重要な理由は、引数の型を特定させるために必要です。
extends による型制約が無い場合、arg がunknownとして考えられるためメソッドにアクセスできません。
const foo = <T>(arg: T) => {
// argが中でどのような型になっているかが分からない
return { value: arg };
};
Generics の型引数が複数あるパターン
Tはstring型に制限され、Kはnumber型に制限されます。Uは、boolean型にデフォルトになっています。
const foo = <T extends string, K extends number, U = boolean>(
foo: T,
bar: K,
baz: U
) => {
return {};
};
Lookup Types
[ ]を使い、Objにアクセスができます。
type Obj = {
a: number;
b: boolean;
};
type Foo = Obj["a"];
Generics と Loolup Types が合わさったパターン
第1引数のTは、第2引数で用いる事ができます。
GenericsとLookup Typesを使ったパターンは、オブジェクトのプロパティに安全にアクセスする方法です。
第1引数でオブジェクトを受け取り、第2引数でオブジェクトのプロパティ名を受け取ります。
また、第2引数で渡すプロパティ名は、オブジェクトが持つプロパティ名のうちの1つである必要があります。
これにより、プロパティ名をタイプセーフに扱うことができます。
このような関数を使うことで、オブジェクトのプロパティに対して安全にアクセスすることができます。
const getProperty = <T, K extends keyof T>(obj: T, key: K) => {
return obj[key];
};
const obj = {
foo: 1,
bar: 2,
baz: 3,
};
// objのプロパティbarの型を取得できる
// hoge は number
const hoge = getProperty(obj, "bar");
Loolup Typesを使いT[K]とする事で、valueの型を簡単につけられます。
const setProperty = <T, K extends keyof T>(obj: T, key: K, value: T[K]) => {
// key のプロパティにvalueを代入している
return (obj[key] = value);
};
const obj = {
foo: 1,
bar: 2,
baz: 3,
};
setProperty(obj, "bar", 100); // obj.barの値が100に変更される
以上となります。
次回はTypeScript最終回です!