0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【TS】ジェネリクスについて

Last updated at Posted at 2024-12-17

ジェネリクスとは

型を引数として受け取ることができる仕組み。

基本

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",
};
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?