型パズルメモ

Gist にしたためていたメモがいよいよ読みにくくなったので転載。
やや古いものや、 `satisfies` も指定したほうがいいものも有るのでそのうち修正予定。

# 引数の値に応じて関数の型をいい感じに推論させてほしい

```typescript
// こんな関数があって
const test = (str: string, isNumber = false): string | number => {
  if (isNumber) {
    return Number(str);
  }
  return str;
};
```

普通に呼びだすとこうなる
```typescript
const t1 = test('aa'); // t1 = string | number
const t2 = test('aa', true); // t2 = string | number
```

これをこうしたい
```typescript
const t1 = test('aa'); // t1 = string
const t2 = test('1', true); // t2 = number
```

これはよく出てくる
```typescript
const test = <T extends boolean>(str: string, isNumber: T = false as T): T extends true ? number : string => {
  if (isNumber) {
    return <T extends true ? number : string>Number(str);
  }
  return <T extends true ? number : string>str;
};

const t1 = test('aa'); // t1 = string | number ...と、 union になったまま
const t2 = test('1', true); // t2 = number
const t3 = test('1', false); // t3 = string ...と、引数指定すればいい感じにはなる
```

このときはこう
```typescript
const test = <T extends boolean = false>(str: string, isNumber: T = false as T): T extends true ? number : string => {
  //                            ~~~~~~~ ここが大事
  if (isNumber) {
    return <T extends true ? number : string>Number(str);
  }
  return <T extends true ? number : string>str;
};
```

利用例
```typescript
const t1 = test('aa'); // t1 = string
const t2 = test('1', true); // t2 = number
// 当たり前だけど test の第一引数に数値以外を渡したら型パズル的にはOKだがランタイムエラーになるので注意
```

# Union

構造体や配列から union type を作るやつ。

```typescript
const genders = {
  Male: 'male',
  Female: 'female',
  Other: 'other',
} as const;
type Gender = typeof genders[keyof typeof genders]; // => 'male' | 'female' | 'other'

const prefs = ['北海道', '青森県', '岩手県', '宮城県', '福島県', '秋田県', '山形県', '略'] as const;
type Pref = typeof prefs[number]; // Pref => '北海道' | '青森県' | 略;
```

# Jest でテストコードでよく使うもの

こんなクラスがあって
```typescript
class Hoge {
  huga(piyo: string): string {
    return `${piyo}.${piyo}`;
  }
}
```

これから↓の型を取り出したい
```typescript
type Huga = (piyo: string) => string;
```

このときはこう
```typescript
type StripMethodType<T extends {}, M extends keyof T> =
  T[M] extends (...args: any) => any ? T[M] : never;

type MethodType = StripMethodType<Hoge, 'huga'>;

// これでもよい。こっちのほうが使いやすいかも
type MethodType = Hoge['huga'];
```

`Hoge.huga` の引数の型を取り出したいなら `Parameters` を使えばよい
```typescript
type MethodParam<T extends {}, M extends keyof T> =
  Parameters<StripMethodType<T, M>>;

type Params = MethodParam<Hoge, 'huga'>; // => [piyo: string]
```

Mock の resolvedValue とかの戻り値をいい感じにしたい
```typescript
(hugaMock as jest.Mock).mockReturnValue('a.a');
//                                      ~~~~ 文字列以外のときは怒ってほしいので↓
(hugaMock as jest.Mock<ReturnType<StripMethodType<Hoge, 'huga'>>>).mockReturnValue('a.a');
```

長ったらしいのでこう

```typescript
type JestMockReturnType<T extends {}, M extends keyof T> =
  T[M] extends (...args: any) => any ? jest.Mock<ReturnType<T[M]>> : never;

(hugaMock as JestMockReturnType<Hoge, 'huga'>).mockReturnValue('a.a');
```

引数に応じた処理を加えたいならこう
```typescript
type Param = MethodParam<Hoge, 'huga'>;
(hugaMock as JestMockReturnType<Hoge, 'huga'>)
  .mockImplementation((piyo: Param[0]) => `${piyo}.${piyo}`);
```

ちなみに単純な string ではなく以下のようにテンプレートリテラルで表すことができる

```typescript
type HugaTemplateLiteral = `${string}.${string}`;
const huga: HugaTemplateLiteral = 'aa'; // => Type '"aa"' is not assignable to type '`${string}.${string}`'
const hoge: HugaTemplateLiteral = 'a.a'; // OK!
```

```typescript
class Responder {
  ok<T>(data: T, response: Express.Response): void {
    response.status(200).json({ data });
  }
}
```

これから↓の型を取り出したい。つまり、 Generics で利用時に型を確定させたい（`T` に何かしらの制約があり、動的に取り出したい

```typescript
type okProps<T> = [T, Express.Response];
```

この記法は文法エラーなので注意
```typescript
type CannotCapture = Parameters<Responder['ok']<{ foo: string }>>;
```

この場合は、三角跳びでキャプチャできた
```typescript
type ResponderOkPrototype<T> = typeof Responder.prototype.ok<T>;
type ResponderOkProps = Parameters<ResponderOkPrototype<{ foo: string }>>;
```


# Set / Map / Record から型を取り出す方法


実践 TypeScript に書いてあった Set の値の型を取り出す型

```typescript
type PickSet<T> = T extends Set<infer I> ? I : never;
```

実践 TypeScript に書いてあった Map の Key を取り出す型

```typescript
type PickMapKeys<T> = T extends Map<infer K, any> ? K : never;
```

（自分が本当に欲しかった） Record の Type の型を取り出す型

```typescript
type PickRecordValueType<T> = T extends Record<any, infer U> ? U : never;
```

利用例
```typescript
type ComplicatedTypeRecord = Record<string, ComplicatedType>;
type PickedRecordValueType = PickRecordValueType<ComplicatedTypeRecord>;
// => PickedRecordValueType: ComplicatedType
```

Record の Key の型を取り出す型
```typescript
type PickRecordKeyType<T> = T extends Record<infer K, any> ? K : never;
```

利用例
```typescript
type ComplicatedTypeRecord = Record<UnionType, string>;
type PickedRecordKeyType = PickRecordKeyType<ComplicatedTypeRecord>;
// => PickedRecordKeyType: UnionType
```