4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Typescript] Generics

4
Posted at

Generics

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

↓関数copyを実行するときに、型を引数として渡してあげることができます。
↓関数名と値の間に<T>と入れることで、Tが、今後どの場面でも使える型になります。
↓例えば値にも、戻り値にもTが使えますし、let user: T;のようにも使えます。

.ts
function copy<T>(value: T): T {
  let user: T;
  return value;
}
console.log(copy<string>('hello'));

じゃあこのTに入る型はどこで渡すかというと、console.log(copy('hello'));のように、
copyと引数の間に<string>として渡してあげます。
そうすると、<T>はstring型になります。

↓このTはパラメーターのように何個も渡せます。
↓T以降はU, Vのように書かれることが多いですが、名前はTやUでなく、何でも構いません。

.ts
function copy<T, U, V>(value: T): T {
  let user: V;
  return value;
}
console.log(copy<string, number, boolean>('hello'));

↓引数がオブジェクトの場合、
↓型アサーションではこのように{ name: string }と付ける必要がありましたが、

.ts
function copy<T>(value: T): T {
  let user: T;
  return value;
}
console.log(copy<{ name: string }>({ name: 'Quill'}));

↓genericsでは型推論してくれます。

.ts
function copy<T>(value: T): T {
  let user: T;
  return value;
}
console.log(copy({ name: 'Quill'}));

↓関数でT, Uと渡していてUについて何も書いていないときは、
↓Uはデフォルトでunknown型になっています。

function copy<T, U>(value: T): T {
  let user: T;
  return value;
}
console.log(copy({ name: 'Quill'}));

型パラメーターに制約を付ける

↓extendsを使います。
↓「オブジェクトかつnameを含んでいて、string型だ」という制約はこう書きます。

function copy<T extends { name: string }>(value: T): T {
  let user: T;
  return value;
}
console.log(copy({ name: 'Quill'}));

オブジェクトのキーのunion型を作る

↓Kは、type K = "number" | "name"というunion型になっています。
↓このように、オブジェクトのキーを取ってunion型にするものをkeyof演算子と言います。

.ts
type K = keyof { name: {}; number: number}

↓例えば、Uの型をTに依存させたいといったときに、
↓U extends keyof Tのようにすると、Uの型は、name | ageのunion型になります。
↓なので、Uの型はnameかageに限定されますし、関数の中でvalue[key];のように使えます。

function copy<T extends { name: string }, U extends keyof T>(value: T, key: U): T {
  value[key];
  return value;
}
console.log(copy({ name: 'Quill', age: 20}, 'age'));

classに対してgenericsを使う方法

↓定義するclass名の後にgenericsを追加します。
↓生成するときはインスタンス化するclass名(今回だったらnew)の後に追加します。

class LightDataBase<T extends string | number | boolean> {
}

const stringLightDataBase = new LightDataBase<string>();

Interfaceに対してgenericsを使う方法

↓interfaceを定義した名前の後に、<T>のように付けることができます。

interface TmpDataBase<T> {
  id: number;
  data: T[];
}

↓じゃあTの型はどこで定義するかというと、ここです。

const tmpDataBase: TmpDataBase<number> = {
  id: 3,
  data: [20]
}

Utility Types

utility = 実用性

型のlibraryです。
Typescriptに内蔵されたジェネリック型になります。

↓例えばPartial型というものがあります。これは、

interface ToDo {
  title: string;
  text: string;
}

type ToDoable = Partial<ToDo>

↓全部optional型になるUtility classです。

type ToDoable = {
    title?: string;
    text?: string;
}

↓Readonly classだったら、全てにreadonlyが付きます。

type ReadTodo = Readonly<ToDo>

type ReadTodo = {
    readonly title: string;
    readonly text: string;
}

Genericsの型のParameterにデフォルトの型を使う

↓Genericsの型のParameterは、TとかUのことです。
↓T = anyのようにすると使えるようになります。

interface ResponseData<T = any> {
  data: T;
  status: number;
}

let tmp: ResponseData;

Mapped Types

型のfor文です。反復させるような処理を行えます。

↓for文と同じくinを使います。

type MappedTypes = {
  [P in 'tomato' | 'pumpkin']: P;
}

↓MappedTypesはiterationしています。

type MappedTypes = {
    tomato: "tomato";
    pumpkin: "pumpkin";
}

↓keyofと併用することもあります。
↓MappedTypesは先ほどと同じ内容です。

interface Vegetables {
  tomato: string;
  pumpkin: string;
}
type MappedTypes = {
  [P in keyof Vegetables ]: P;
}

↓genericsを用いて、MappedTypesを色々なところで使えるようにする事もよくあります。

type MappedTypes <T> = {
  [P in keyof T ]: P;
}

↓また、MappedTypesは先頭にreadonly, 末尾に?を付けることもできます。

type MappedTypes <T> = {
  readonly [P in keyof Vegetables ]?: P;
}

----------------------------------------------------
//結果

type MappedTypes<T> = {
    readonly tomato?: "tomato";
    readonly pumpkin?: "pumpkin";
}

↓readonlyの頭に ー を付けると、Vegetablesのreadonlyが全て打ち消されます。
↓?の頭に ー を付けると、Vegetablesのoptional型が全て打ち消されます。

type MappedTypes <T> = {
  -readonly [P in keyof Vegetables ]-?: P;
}

Conditional Types

型のif文みたいなものです。

↓tomato型が、string型に代入できるのであれば、number、できないのであればboolean
↓といった意味になります。

type ConditionalTypes = 'tomato' extends string ? number : boolean;

↓extendsを基準とし、左が右に代入できるような方向性のとき、Rが使えるという意味です。
↓infer Rはanyのようなもので、何でもOKです。
↓の上で、{ tomato: 'tomato' }から{ tomato: infer R }の型を推論します。
↓infer Rが、{ tomato: 'tomato' }に最も近くなるような型になります。

type ConditionalTypesInfer = { tomato: 'tomato' } extends { tomato: infer R } ? R : boolean;

よって今回は、ConditionalTypesInferが"tomato"型になります。

DistributiveConditionalTypes

↓DistributiveConditionalTypesというものがあって、
↓extendsの左側がunion型になっているとき、どうなるの?という意味を表現します。
↓結論、分配されます。

type DistributiveConditionalTypes<T> = T extends 'tomato' ? number : boolean;
let tmp: DistributiveConditionalTypes<'tomato' | 'pumpkin'>

つまり、number | booleanのunion型になります。
また、DistributiveConditionalTypesを使うときは、genericsを用います。

4
3
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
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?