概要
オンラインサロン IT_KINGDOM で、typescript について学んだことを備忘録としてメモしていきます。
学習内容
- Generics とは
- 初期値の設定
- extends による型の制約
- 関数の Generics による型推論
- Generics における複数の型引数
- LookupTypes(ルックアップ型)
- javascript メソッドでの Generics の活用
Generics とは
Generics とは、あらかじめ抽象的な型指定を行うこと
最初の型指定を<T>
とすることで、後でT
に任意の型を指定できる(T
は任意の文字列で OK)
// Genericsの型としてTを宣言し、valueの型にTを指定
export type Foo<T> = {
value: T;
};
const foo: Foo<number> = {
// fooでは、Tの型としてnumber型を指定
value: 3,
};
const foo2: Foo<string> = {
// foo2では、Tの型としてstring型を指定
value: "",
};
const foo3: Foo<number[]> = {
// foo3では、Tの型としてnumber[]型を指定
value: [1, 2, 3],
};
// UserオブジェクトのプロパティstateをGenericsにする
export type User<T> = {
name: string;
state: T;
};
// Japanese型ではstateを東京都 or 大阪府、American型ではCA or NYに制約
type Japanese = User<"東京都" | "大阪府">;
type American = User<"CA" | "NY">;
const user1: Japanese = {
name: "田中",
state: "東京都",
};
const user2: American = {
name: "Jonny",
state: "CA",
};
初期値の設定
Generics では<T>
の初期値となる型を設定できる。
初期値を設定した場合、型宣言時に<T>
の具体的な型を指定しなければ、初期値が入る。
// <T>の初期値をstring型に設定
export type Foo<T = string> = {
value: T;
};
// <T>の型を指定してないため、valueはstring型になる
const foo1: Foo = {
value: "",
};
// <T>の型をnumber型としたため、valueはnumber型となる
const foo2: Foo<number> = {
value: 111,
};
extends による型の制約
<T>
にextends + 型
を追加することで、<T>
はextends
で指定されたインターフェースを満たす型のみ取れるようになる。
// Tの型をstring型で制約する
export type Foo<T extends string> = {
value: T;
};
// string型なのでOK
const foo1: Foo<string> = {
value: "",
};
// number型はstring型を満たさないためエラー
const foo2: Foo<number> = {
value: 111,
};
// "bar"はstring型を満たすためOK
const foo3: Foo<"bar"> = {
value: "bar",
};
また、extends
を使った型制約時も、型の初期値を指定できる
// extendsの初期値の指定
// Tの型をstring型 or number型に制約し、初期値をstring型に設定
export type Foo<T extends string | number = string> = {
value: T;
};
// 型の初期値はstringを指定されているため、valueはstring型になる
const foo1: Foo = {
value: "",
};
// numberのためOK
const foo2: Foo<number> = {
value: 111,
};
extends
で複数の型に制約した際、特定の型のみに依存する機能を用いるとエラーになる。
特定の型に依存する機能を用いたい場合は、型の絞り込みを行う。
function foo<T extends string | number>(arg: T) {
// toUpperCase()はstringのメソッドなので、stringに絞り込みを行う
if (typeof arg === "string") {
return { value: arg.toUpperCase() }; // stringに絞り込みが行われているためOK
}
return { value: arg.toUpperCase() }; // argの型がnumberの可能性があるためエラー
}
関数の Generics による型推論
関数の Generics では、関数宣言時に型が明示されていない場合、暗黙的に型推論される
// 暗黙的な型推論
// 引数argの型をGenericsで設定
function foo<T>(arg: T) {
return { value: arg };
}
// 関数のGenericsの型を推論する
const foo1 = foo<string | null>(""); // 引数の型はstring または nullとなる
const foo2 = foo(1); // 引数の型は暗黙的にnumberとなる
const foo3 = foo([false]); // 引数の型は暗黙的に[boolean]になる
Generics における複数の型引数
Generics の型は、複数指定することができる。(例:<T, K, U>
)
// Genericsの型引数を複数持つ
const foo = <T, K, U>(foo: T, bar: K, baz: U) => {
return {};
};
// 各々の型に対してextendsによる制約をかけることもできる
const foo2 = <T extends string, K extends number, U = boolean>(
foo: T,
bar: K,
baz: U
) => {
return {};
};
LookupTypes(ルックアップ型)
オブジェクトのプロパティ(key)に対応する値(value)の型にアクセスする型を Lookup Types(ルックアップ型)という。
オブジェクトのプロパティを Generics で指定する場合、extends keyof
による型の制約が必要となる。
type Obj = {
a: number;
b: boolean;
};
type Foo = Obj["a"]; // Fooの型はnumberになる
const getProperty = <T, K>(obj: T, key: K) => {
return obj[key]; // 型Kが型Tのプロパティ(key)として設定されてないため、エラー
};
const getProperty2 = <T, K extends keyof T>(obj: T, key: K) => {
return obj[key]; // 型Kが型Tのプロパティ(key)として設定されているため、OK
};
const setProperty = <T, K extends keyof T>(obj: T, key: K, value: T[K]) => {
obj[key] = value;
};
const obj = {
foo: 1,
bar: 2,
baz: 3,
};
setProperty(obj, "bar", 100); // obj[bar]の型はnumber型のため、100はOK
setProperty(obj, "bar", "bar2"); // obj[bar]の型はnumber型のため、"bar2"はエラー
javascript メソッドでの Generics の活用
javascript のメソッド(map()
など)でも暗黙的に Generics が使われている。
const foo = [1, 2, 3].map((v) => v); // fooの型はnumber[]になる。
const foo2 = [1, 2, 3].map((v) => v.toString()); // foo2の型はstring[]になる。
参考サイト
【TypeScript】Generics(ジェネリックス)を理解する
サバイバル TypeScript
駆け足エンジニアの記録
IT Kingdom