type-challengesの解説(easy編)

# 概要
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2672529/00b7cb2f-be5d-8936-1fce-81df50794776.png)
[type-challenges](https://github.com/type-challenges/type-challenges)を解き、考える上でポイントだった部分を書き残していきます。
easy編をすべて解けばTypeScriptの基本的な構文については理解したと言えるレベルになれると思います。
easy編はたった14問なのでTypeScriptを勉強している方はチャレンジすると良いと思います。

# type-challengesとは
https://github.com/type-challenges/type-challenges
上記リポジトリにある、TypeScriptの型定義に関する問題集です。
全部で150問あり、easy、medium、hard、extremeの4レベルに分けられています。
日本語の問題もあり、解答例もgithubのissueで利用することができるます。
easyもそこそこ難しく、TypeScriptの型定義で使われる文法すべてを最低限知っておく必要があります。
TypeScriptの学習に関してはMicrosoftの[TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html)が充実しています。（一通りこちらのドキュメントを読んでから問題に取り組むことを推奨します。）

# 環境構築
- pnpmのインストール
- pnpm install && pnpm generate
- 追加ライブラリのインストール(typescriptを入れてtscコマンドを使えるようにすると、テストケースをパスしたかチェックできます)
- eslintとprettierの設定

# 各問題の解説
## 0004
[Indexed Access Types](https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html)と[Mapped Types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html)を理解できれば解けるはずです。
`[k in K]`で`K`としてユニオン型を使えばそのユニオン型に含まれるkeyを繰り返す動きになります。(Indexed Access Types)
`T[k]`でオブジェクト型`T`の`k`フィールドの型を取り出すことができます。(Mapped Access Types)
genericsでKの型をTのkeyに制限してやる（`K extends keyof T`）ことのがポイントです。（そうしないとエラーケースをパスしません。）
```typescript:正解例
type MyPick<T, K extends keyof T> = {
  [k in K]: T[k]
}
```


## 00007
オブジェクト型のプロパティの前に`readonly`を付けるとそのプロパティをreadonly型にできることを使います。
[Handbookのこのあたり](https://www.typescriptlang.org/docs/handbook/2/objects.html#readonly-properties)を理解すれば解けます。
[Mapping Modiffiers](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers)を使えばオブジェクトのプロパティには`readonly`か否か、optionalか否かの属性を付与/剥奪することができます。
```typescript:Mapping Modifierの使用例
// readonly付与
type MakeReadonly<Type> = {
  readonly [Property in keyof Type]: Type[Property];
};
// readonly剥奪
type MakeUnReadonly<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};
// optional付与
type MakeOptional<Type> = {
  [Property in keyof Type]?: Type[Property];
};
// optional剥奪
type MakeUnOptional<Type> = {
  readonly [Property in keyof Type]-?: Type[Property];
};
```

```typescript:正解例
type MyReadonly<T> = {
  readonly [K in keyof T]: T[K]
}
```
## 00011
ここまでの問題を理解できていれば解けるはずです。
```typescript:正解例
type TupleToObject<T extends readonly (string | number | symbol)[]> = {
  [k in T[number]]: k;
};
```

## 00014
`[]`で要素数0の配列の型を表現できるのがポイント。要素数が0か否かで場合分けして返す。
```typescript:間違い例1
type First<T extends any[]> = T[0];
Expect<Equal<First<[]>, never>> // undefinedになってしまう
```
```typescript:間違い例2
type First<T extends any[]> = T[0] extends undefined ? never : T[0];
Expect<Equal<First<[undefined]>, undefined>> // neverになってしまう
```
```typescript:正解例
type First<T extends any[]> = T extends [] ? never : T[0];
```

## 00018
Indexed Access Typesで`length`プロパティを取得すれば良い。genericsで受け取る型が何らかの配列であるという制限をするために、`extends`で配列に限定することがポイント
```typescript:間違い例
type Length<T> = T['length'];

// @ts-expect-error
Length<5> // errorがあがらない
```

genericsの入力型に配列であるという条件をつけてやればよい

```typescript:正解例
type Length<T extends readonly any[]> = T['length'];
```

## 00043
Uであればneverを、UでなければTを返せばOK

```typescript:正解例
type MyExclude<T, U> = T extends U ? never : T;
```
## 00189
Promiseが自分自身を内包し得る型であるため、再帰的な型定義を考えたいです。ジェネリクスで受け取った型TがPromiseならばその中身の型Uに対して再帰的に自身を呼び出し、Non-Promiseならばそのまま返すような型定義RAwaitedを作るのがポイントです。
このままだとNon-Promiseをそのまま渡される場合のエラーケースに対応できないのでMyAwaitedの中でRwaitedを使うようにし、MyAwaitedでは条件型を使ってエラーを起こすようにしてやればよいです。
実際テストケースに`Promise<Promise<Promise<T>>>`みたいなパターンも含まれているのでこれに対応する必要があります。
```typescript:正解例
type RAwaited<T> = T extends PromiseLike<infer U> ? RAwaited<U> : T;
type MyAwaited<T extends PromiseLike<any>> = RAwaited<T>;
```

## 00268
true型、false型を使って条件分岐するだけ

```typescript:正解例
type If<C extends boolean, T, F> = C extends true ? T : F;
```

## 00533
型におけるスプレッド構文を使うことがポイント
参考：https://zenn.dev/cookiegg/articles/typescript-spread-type
`(1 | 2)[]`と`[1, 2]`の型は違うことを理解する必要がある
```typescript:間違い例
// 以下だとConcat<[1, 2], []>の結果が(1 | 2)[]となってしまうのでNG
type Concat<T extends any[], U extends any[]> = (T[number] | U[number])[];
```

```typescript:正解例
type Concat<T extends any[], U extends any[]> = [...T, ...U];
```


## 00898
easyレベルの問題の中で最高難易度の問題。
ポイントは２つ
1. 型の一致判定をどうするか？
1. 配列型の一つ一つの要素の型との一致比較をどうするか

1についてはextendsは使えない。extendsはあくまでextends(拡張)した型であることを判定するものであるため。
そこで少しずるいが、type-challengesのテストケースを見ると`Equal<A,B>`なる型が用意されてあり、これを利用すれば型の一致判定を行うことができる。もちろん、自分で同じような型を実装するのもあり。type-challengesの`Equal`型の実装を見て理解するのも勉強になると思うのでぜひ。

2については00189でやったような再帰的な型定義を使うことで解決できる。

上記ポイントに加えextends構文をきっちり理解し、inferを使えるかもポイント。extends構文とinferの使い方についてはこちらの記事が分かりやすかった。[TypeScriptのextendsってなんなん？](https://zenn.dev/nbr41to/articles/7d2e7c4e31c54c)

またタプル型の残余展開を利用し、タプル型の要素を1番目とそれ以外に分ける方法も知っておく必要がある。残余展開については[Handbook
参照](https://www.typescriptlang.org/docs/handbook/2/objects.html#the-array-type)

```typescript:間違い例
type Includes<T extends readonly any[], U> = U extends T[number] ? true : false;
```

```typescript:正解例
type Includes<T extends readonly any[], U> = T extends [infer T1, ...infer Ts]
  ? Equal<T1, U> extends true
    ? true
    : Includes<Ts, U>
  : false;
```

## 03057
簡単。タプル型の[残余展開](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types)を知っていれば解けるはず。
```typescript:正解例
type Push<T extends any[], U> = [...T, U];
```


## 03060
簡単。[03057](#-03057)と同様。
```typescript:正解例
type Unshift<T extends unknown[], U> = [U, ...T];
```


## 03312
関数Tに何個存在するか分からない引数をまとめてinferする方法がわかれば解ける。正解を言ってしまうと、適当に引数に名前をつけて(正解例では`arg`)、その引数を残余展開すればよい。`...`を付けるのが型名ではなく変数名の前であることがポイントで、これによりTは引数の方の配列になる。
```typescript:正解例
type MyParameters<T> = T extends (...args: infer I) => any ? I : never;
```