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


<h1>ジェネリクス</h1>
型を引数として受け取る

<h2>基本</h2>
関数実行時に引数で型を渡すことができる
それを関数内で使える

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

copy<string>('hello');
```

<h5>具体例</h5>

```typescript
// 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('文字列２'); //=> "文字列２"
```

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

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

copy({ name: 'hello' }.name);
```

<h2>複数の型引数を定義する</h2>

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

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

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

```typescript
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' }
```

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

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

copy({ name: 'hello' });
```

<h2>keyof / T[K]</h2>
オブジェクトのプロパティ名をliteral Typesとして一覧で取得できるもの

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

type UserKey = keyof User;
```

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

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

```typescript
type UserName = User['name'];
```

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

<h4>具体例1</h4>

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

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

<h4>具体例2</h4>

```typescript
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;
}
```

<h2>keyof / typeof</h2>

```typescript
const obj = {
  foo: 'aaa',
  111: 222,
};

type test = keyof typeof obj;

//type test = "foo" | 111
```

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

<h4>柔軟なジェネリクス</h4>
U extends keyof T でUはnameとageのユニオン型
こうすることで、第二引数にnameまたはage以外とれなくなる

```typescript
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;
```

<h2>classにgenericsを適用</h2>

```typescript
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')
];
```

<h2>型のfor文であるMapped Types</h2>

```typescript
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;
};
```

<h4>具体例</h4>

```typescript

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

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

//T１の型
{ foo?: number; bar?: string; }
```

<h2>型のif文であるConditional Types</h2>
イメージ三項演算子
トマト型がstringに代入できればnumberできなければboolean

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

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

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

```typescript
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();
```

<h2>Infer</h2>

```typescript
//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;
```

<h4>具体例</h4>

```typescript
//まずは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);
```

<h2>distribution</h2>

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

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

<h2></h2>

```typescript

```

<h2></h2>

```typescript

```

<h2></h2>

```typescript

```