ジェネリクスとは
型を引数として受け取ることができる仕組み。
基本
function copy<T>(value: T) {
return value;
}
const hello = copy<string>("Hello");
console.log(hello);
// 型推論してくれる
function copy<T>(value: T) {
return value;
}
// <T>を省略すると、以下と同じ
// const Apple = copy<{ name: "apple"; count: 5 }>({ name: "apple", count: 5 });
const Apple = copy({ name: "apple", count: 5 });
console.log(Apple);
// ジェネリクスを複数指定した関数を呼び出す際に、
// ジェネリクスを1つしか呼び出さなくてもエラーにならない
// 2つ目のUは"unknown"になる
function copy<T, U>(value: T) {
return value;
}
const Apple = copy("apple");
Union型とジェネリクスの違い
- Union型だと自由度が高すぎる
Union型の場合
function addArr(arr: string[] | number[], item: string | number) {
const newArr = [...arr];
newArr.push(item);
return newArr;
}
// 全要素がstring型なのに、(string | number)[]型
const foo = addArr(["apple", "banana"], "peach");
// (string | number)[]型だから文字列と数字が混在できてしまう
const boo = addArr(["apple", "banana"], 1);
ジェネリクスの場合
function addArr<T>(arr: T[], item: T) {
const newArr = [...arr];
newArr.push(item);
return newArr;
}
// const foo: string[]
const foo = addArr(["apple", "banana"], "peach");
// エラー:numberはstring[]に代入できない
const boo = addArr(["apple", "banana"], 1);
ジェネリクスに制約をつける
// Tに渡せる型は"{name:string}"を持った型のみ
function copy<T extends { name: string }>(value: T) {
return value;
}
// OK
const foo = copy({ name: "apple" });
// これもOK
const foo2 = copy({ name: "apple", count: 5 });
// これはNG
const boo = copy({ count: 5 });
extendsをつけたので関数の中で型のプロパティを使える
function copy<T extends { name: string }>(value: T) {
// valueには必ずstring型のnameプロパティがある
const _name = value.name;
return value;
}
const foo = copy({ name: "apple" });
ジェネリクスとkeyofの組み合わせ
第2引数に指定する型を第1引数のオブジェクトのユニオン型に限定する
function copy<T extends { name: string }, U extends keyof T>(value: T, k: U) {
const count = value[k];
return count;
}
// 第2引数に指定する値は、第1引数のプロパティ名に限定する
// OK
const foo = copy({ name: "apple", count: 15 }, "count");
// NG:"aaa"は第1引数のオブジェクトのプロパティ名に存在しない
const boo = copy({ name: "apple", count: 15 }, "aaa");
ジェネリクスをclassで使う
class DataBase<T extends string | number | boolean> {
private data: T[] = [];
add(item: T) {
this.data.push(item);
}
remove(item: T) {
this.data.splice(this.data.indexOf(item), 1);
}
get() {
console.log(this.data);
}
}
const stringDatabase = new DataBase<string>();
stringDatabase.add("apple");
stringDatabase.add("banana");
stringDatabase.add("orange");
// [ 'apple', 'banana', 'orange' ]
stringDatabase.get();
stringDatabase.remove("orange");
// [ 'apple', 'banana' ]
stringDatabase.get();
ジェネリクスとPromise
型を指定しないとPromiseとなる
// const promise: Promise<unknown>
const promise = new Promise(resolve => {})
型指定
// const promise: Promise<string>
const promise = new Promise<string>((resolve) => {
resolve("Hello");
});
// HELLO
promise.then((data) => {
console.log(data.toUpperCase());
});
ジェネリクスにデフォルトの型を指定
interface ResponseData<T = string> {
data: T;
status: string;
}
// ジェネリクス未指定でstring型となる
const res: ResponseData = {
data: "APPLE",
status: "OK",
};
extendsで制約をつけつつデフォルトの型も指定可能
interface ResponseData<
T extends { msg: string } = { msg: string; code: number }
> {
data: T;
status: string;
}
const res: ResponseData<{ msg: string; header: string }> = {
data: { msg: "完了した", header: "cors" },
status: "OK",
};