LoginSignup
0
2

More than 1 year has passed since last update.

TypeScriptチートシート5(ジェネリクス)

Last updated at Posted at 2022-01-07

ジェネリクス

型を引数として受け取る

基本

関数実行時に引数で型を渡すことができる
それを関数内で使える

function copy<T>(value: T): T {
  let hone: T;
  return value;
}

copy<string>("hello");

具体例
// number型
function test(arg: number): number {
  return arg;
}

// string型
function test2(arg: string): string {
  return arg;
}

test(1); //=> 1
test2("文字列"); //=> 文字列

//上の2つは共通化できそう

function test<T>(arg: T): T {
  return arg;
}

test<number>(1); //=> 1
test<string>("文字列"); //=> 文字列

//※ Genericsでも型推論ができるので、引数から型が明示的にわかる場合は省略が可能
test("文字列2"); //=> "文字列2"

型推論

引数に型を書かなくても型推論してくれる
この時の型は{ name : string}

function copy<T>(value: T): T {
  return value;
}

copy({name:"hello"}.name);

複数の型引数を定義する

function test<T, U, P>(arg1:T, arg2: U, arg3: P): P {
  return arg3;
}

//※ Genericsでも型推論ができるので、引数から型が明示的にわかる場合は省略が可能
test("文字列", true, 4); //=> 4

プロパティを増やす関数

T & { id: string }はインターセクション型であり、オブジェクトに新しいプロパティを増やしたい場合の典型的な方法
Tと{ id: string }両方の型を持つという意味

function plus<T>(obj: T): T & { id: string } {
  const id = "ID";
  return {
    ...obj,
    id,
  };
}

const obj1: {
  id: string;
  foo: number;
} = plus({ foo: 123 });

const obj2: {
  id: string;
  num: number;
  hoge: boolean;
} = plus({
  num: 0,
  hoge: true,
});

console.log(obj1);
console.log(obj2);

//コンソール
{ foo: 123, id: 'ID' }
{ num: 0, hoge: true, id: 'ID' }

extends

パラメーターに制約を付ける
こう書くと必ずオブジェクトでnameを含んでいることになる

function copy<T extends { name: string }>(value: T): T {
  return value;
}

copy({ name: "hello" });

keyof / T[K]

オブジェクトのプロパティ名をliteral Typesとして一覧で取得できるもの

interface User {
  name: string;
  age: number;
}

type UserKey = keyof User;

UserKey は 'name' | 'age' というtype

K は、keyof T を満たすtypeであれば良いので、例えば

type UserName = User['name'];

とすると、UserName のtypeは string になる。 User['age'] とすれば number。

具体例1

interface Observable<T> {
    pluck<K extends keyof T>(key: K): Observable<T[K]>;
}

let user: Observable<User>;
let name = user.pluck('name'); 
// Observable<string>

具体例2

type PropNullable<T> = {[P in keyof T]: T[P] | null};

interface Foo {
  foo: string;
  bar: number;
}

const obj: PropNullable<Foo> = {
  foo: 'foobar',
  bar: null,
};

//PropNullable<Foo>の型
{foo: string | null; bar: number | null; }

keyof / typeof

const obj = {
    foo: "aaa",
    111:222
}

type test = keyof typeof obj

//type test = "foo" | 111

typeofでobjの型を取得
keyofでliteral typesの一覧を取得

柔軟なジェネリクス

U extends keyof T でUはnameとageのユニオン型
こうすることで、第二引数にnameまたはage以外とれなくなる

function copy<T, U extends keyof T>(val: T, key: U) {
    return val[key]
}

const suda = copy({ name: 'masaki', age: 28 }, 'name')
console.log(suda)
// コンソール
masaki

classにgenericsを適用

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() {
    return this.data;
  }
}

const stringDataBase = new DataBase<string>();
stringDataBase.add("apple");
stringDataBase.add('banana')
stringDataBase.add('grape')
stringDataBase.remove('banana')

console.log(stringDataBase.get())
//コンソール
[ 'apple', 'grape' ]

型のfor文であるMapped Types

interface person {
    masaki: string,
    age:28
}

type MappedTypes = {
    [P in keyof person]:P
}

//MappedTypesの型
type MappedTypes = {
    masaki: "masaki";
    age: "age";
}

type MappedTypes = {
    [P in keyof person]:string
}

//MappedTypesの型
type MappedTypes = {
    masaki: string;
    age: string;
}

具体例


type MyPartial<T> = { [K in keyof T]?: T[K] };

type T1 = MyPartial<{
    foo: number;
    bar: string;
  }>;

//T1の型
{ foo?: number; bar?: string; }

型のif文であるConditional Types

イメージ三項演算子
トマト型がstringに代入できればnumberできなければboolean

type ConditionalTypes_1 = "tomato" extends string ? number : boolean;
//現在の型
type ConditionalTypes_1 = number

type ConditionalTypes_2 = string extends "tomato" ? number : boolean;
//現在の型
type ConditionalTypes_2 = boolean

具体例

Aがundefinedかどうかで動作を変えたい
undefined型がA型の部分型であれば第一引数はあってもなくてもよい

type Func<A, R> = undefined extends A ? (arg?: A) => R : (arg: A) => R;

// 使用例
//undefined型がA型の部分型ではないのでfalse
const f1: Func<number, number> = (num) => num + 10;
const v1: number = f1(10);

//Aがundefinedなのでtrue
const f2: Func<undefined, number> = () => 0;
const v2: number = f2();
const v3: number = f2(undefined);

//Aはnumber | undefinedなのでtrue
const f3: Func<number | undefined, number> = num => (num || 0) + 10;
const v4: number = f3(123);
const v5: number = f3();

// エラー例
//この場合は引数が必要なのでエラー
const v6: number = f1();

Infer

//tomatoがtomatoプロパティをもったらtomato型
//なければ、boolean
type ConditionalTypesInfer = { tomato: "tomato" } extends { tomato: infer R }? R: boolean;

//tomatoがtomatoプロパティをもったらstring型
//なければ、boolean
type ConditionalTypesInfer = { tomato: "tomato" } extends { tomato: infer R }? string: boolean;

具体例

//まずはTをオブジェクトに限定
//引数にfooがあったらfooのプロパティ型、なければunknown
//最後にas anyを付けることでfooがないときのエラー回避
function getFoo<T extends object>(
  obj: T
): T extends { foo: infer E } ? E : unknown {
  return (obj as any).foo;
}

// 使用例
// numはnumber型
const num = getFoo({
  foo: 123
});
// strはstring型
const str = getFoo({
  foo: "hoge",
  bar: 0
});
// unkはunknown型
const unk = getFoo({
  hoge: true
});

// エラー例
getFoo(123);
getFoo(null);

distribution

1:引数'tomato'は'tomato'に代入できるのでnumber
2:引数'pump'はできないのでboolean

type Conditional<T> = T extends 'tomato' ? number : boolean
let temp : Conditional<'tomato'|'pump'>
//tempの型
let temp: number | boolean

0
2
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
2