[TS]TypeChallengesからTypeScriptを復習しましょう_1

## はじめに
このシーズンのblogは自身がTypeScriptを再学習した記録です。Type Challengesを練習することで、TypeScriptの基礎知識を復習しながら補充します。
この記事の対象はTypeScript基礎知識は持っている初心者です。ゼロからの方は、TypeScriptの基本型、ユニオン型、InterfaceとTypeの相関概念を学習した後、この記事を読んでみてみてください。
## [4. Pick](https://github.com/type-challenges/type-challenges/blob/main/questions/00004-easy-pick/README.md)
```ts
// Challenge
import type { Equal, Expect } from './test-utils'
type cases = [
	Expect<Equal<Expected1, MyPick<Todo, 'title'>>>,
	Expect<Equal<Expected2, MyPick<Todo, 'title' | 'completed'>>>,
	// @ts-expect-error
	MyPick<Todo, 'title' | 'completed' | 'invalid'>,
]
interface Todo {
	title: string
	description: string
	completed: boolean
}
interface Expected1 {
	title: string
}
interface Expected2 {
	title: string
	completed: boolean
}

// Answer
type MyPick<T, K extends keyof T> = {
	[P in K]: T[P]
}
```
### 回答のアプローチ
1. 汎用パラメータの定義：`MyPicK`には2つの汎用パラメータ、`T`と`K`が必要とします。ここで、`T`はオブジェクトタイプで表し、`K`はTの属性キーのユニオン型となります。`extends`を使用して、`K`が`T`のキーのサブセットであることを保証します。
2. マッピング処理：マッピング型を使用して`K`内の各キーに対して繰り返し処理を行います。マッピング型の構文は`[P in K]`で、ここで`P`は`K`のユニオン型に含まれる各プロパティキーを表します。
3. インデックスアクセス型：`T`内の各プロパティキー`P`に対して、`T[P]`を使用して対応する形を取得します。
### 相関ポイント
- **extends**：ジェネリック型や条件型などうで型の制約を定義する際に使用されます。`extends`を使用することで、特定の型のサブセットかどうかを指定したり、ある型が他の型に代入可能かをチェックすることができます。
```ts
// ジェネリック型で使用例
function identity<T extends number>(arg: T):T {
	return arg
}

// 条件型で使用例
type IsString<T> = T extends string ? true : false

type IsStringCheck1 = IsString<string>; // true
type IsStringCheck2 = IsString<number>; // false
```
- **keyof**：TypeScriptのキーワードで、オブジェクト型の全てのキーを取得するために使用されます。取得結果は、それらのキーを文字列リテラルのユニオン型として表します。
```ts
inerface Person {
	name: string;
	age: number;
	hasDriverLicense: boolean
}

type PersonKeys = keyof Person; // 'name' | 'age' | 'hasDriverLicense'
```
## [7.Readonly](https://github.com/type-challenges/type-challenges/blob/main/questions/00007-easy-readonly/README.md#readonly----)
```ts
// ============= Test Cases =============
type cases = [
	Expect<Equal<MyReadonly<Todo1>, Readonly<Todo1>>>,
]
interface Todo1 {
	title: string
	description: string
	completed: boolean
	meta: {
	author: string
	}
}
// ============= Answer =============
type MyReadonly<T> = {
	readonly [P in keyof T]: T[P]
}
```
### 解答アプローチ
取得したいのタイプは処理前とキー及び型は全て同じですが、`readonly`修飾子を使用してオブジェクトのプロパティを読み取り専用に変更します。これを実現するたには、マッピング処理を利用して`readonly`修飾子各プロパティに追加します。

## [8.Readonly2](https://github.com/type-challenges/type-challenges/blob/main/questions/00008-medium-readonly-2/README.md)
```ts
type cases = [
Expect<Alike<MyReadonly2<Todo1>, Readonly<Todo1>>>,
Expect<Alike<MyReadonly2<Todo1, 'title' | 'description'>, Expected>>,
Expect<Alike<MyReadonly2<Todo2, 'title' | 'description'>, Expected>>,
Expect<Alike<MyReadonly2<Todo2, 'description' >, Expected>>,
]
// @ts-expect-error
type error = MyReadonly2<Todo1, 'title' | 'invalid'>
interface Todo1 {
	title: string
	description?: string
	completed: boolean
}
interface Todo2 {
	readonly title: string
	description?: string
	completed: boolean
}
interface Expected {
	readonly title: string
	readonly description?: string
	completed: boolean
}
// ============= Answer =============
type MyReadonly2<T, K extends keyof T = keyof T> = {
	readonly [P in keyof T as P extends K ? P : never]: T[P]
} & {
	[P in keyof T as P extends K ? never : P]: T[P]
}
```
### 解決アプローチ
Readonly2は、オブジェクトのプロパティを読み取り専用にする型ですが、上記のReadonlyとの違いは、オプションとしてプロパティのキーの部分集合`K`を指定できる点です。指定した`K`のキーに対応するプロパティを読み取り専用にし、`K`が指定されていない場合は、全てのプロパティを読み取り専用にします。

だから、Readonly2を実現するためには、以下の二つのステップです：
1. 引数`K`はオブジェクト`T`のキーに存在するかをチェックします
	- `K`が`T`のキーと互換性があるかどうかを `extends`を使用してチェックします。
	-  `K　= keyof T` というデフォルト型指定は、`K`が特に指定されていない場合に`T`の全てのキーをデフォルト値として割り当てるために使われます。
2. 読み取り専用にするプロパティとするしないプロパティを区別します
	 - `P in keyof T as P extends K ? P : never` `T`とは、`T`のキー`P`を調べて、`P`が`K`に含まれていれば、それを読み取り専用にするキーとして扱います。含まれていなけてば、何もしません。
	- `&`を使用して、読み取り専用にしないプロパティを組み合わせる際に、先ほどの判定を反転されたロジックを使います。
## [9.DeepReadonly](https://github.com/type-challenges/type-challenges/blob/main/questions/00009-medium-deep-readonly/README.md)
```ts
type cases = [
	Expect<Equal<DeepReadonly<X1>, Expected1>>,
	Expect<Equal<DeepReadonly<X2>, Expected2>>,
]
type X1 = {
	a: () => 22
	b: string
	c: {
		d: boolean
		e: {
			g: {
				h: {
					i: true
					j: 'string'
					}
				k: 'hello'
			}
			l: [
				'hi',
				{
					m: ['hey']
				},
			]
		}
	}
}
type X2 = { a: string } | { b: number }
type Expected1 = {
	readonly a: () => 22
	readonly b: string
	readonly c: {
	readonly d: boolean
		readonly e: {
			readonly g: {
				readonly h: {
					readonly i: true
					readonly j: 'string'
				}
				readonly k: 'hello'
			}
			readonly l: readonly [
				'hi',
				{
					readonly m: readonly ['hey']
				},
			]
		}
	}
}
type Expected2 = { readonly a: string } | { readonly b: number }
// ============= ANSWER =============
type DeepReadonly<T> = {
	readonly [P in keyof T]: T[P] extends Function ? T[P] : DeepReadonly<T[P]>
}
```
### 解答アプローチ
`DeepReadonly`は、オブジェクト内の全てのプロパティを再起的に読み取り専用にするための方です。
1. マッピング処理：`Readonly`と同様に、`T`の各キーに対していマッピングを行い、それらを読み取り専用にします。
2. 条件型と関数のチェック：`T`のプロパティは大きく分けて三種類あります：プリミティブ型、関数型とオブジェクト型です。プリミティブ型と関数型のプロパティには単純に`readonly`を適用するだけで済みです。しかし、オブジェクト型は、そのプロパティ自体だけではなく内部の全てのプロパティも読み取り専用にする必要があります。そのため、プロパティが関数でない場合は、`T[P]`を再帰的に`DeepReadonly`を適用します。

## [189.Awaited](https://github.com/type-challenges/type-challenges/blob/main/questions/00189-easy-awaited/README.md)
```ts
// ============= Test Cases =============
type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
type Z1 = Promise<Promise<Promise<string | boolean>>>
type T = { then: (onfulfilled: (arg: number) => any) => any }

type cases = [
Expect<Equal<MyAwaited<X>, string>>,
Expect<Equal<MyAwaited<Y>, { field: number }>>,
Expect<Equal<MyAwaited<Z>, string | number>>,
Expect<Equal<MyAwaited<Z1>, string | boolean>>,
Expect<Equal<MyAwaited<T>, number>>,
]
// @ts-expect-error
type error = MyAwaited<number>

// ============= Answer =============
type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer P> ?
P extends PromiseLike<any> ? MyAwaited<P> : P : never
```
### 解答アプローチ
1. 分析した例によれば、入力型は`Promise`だけじゃなくて、`then`を含む`Promise`に似た型も考慮に入れるべきであるため、入力型は`T extends PromiseLike<any>`を使用することが適切です。
2. `PromiseLike`の内部内容の型を推定することが必要ですので、`infer P` を使います。
3. 推定型`P`と内部構造がまだ`PromiseLike`型の可能性があるかため、内部構造まだチェックして、同じ`PromiseLike`型であれば、再帰的に処理を続けます。そして、`PromiseLike`型でない場合には、その型を返すようにします。
### 相関ポイント
- **PromiseLike**:`then`を持って、`Promise`の基本的な行為に似たオブジェクトです。
```ts
let promiseLike: PromiseLike<number> = {
    then: () => {
	    return onfulfilled(42);
    }
};
promiseLike.then(value => console.log(value)); // 42
```
- **infer**:条件型内で使用されるキーワードです。型をダイナミックに推論するために使います
```ts
type ExtractPromiseType<T> = T extends PromiseLike<infer U> ? U : never;
type ResolvedType = ExtractPromiseType<PromiseLike<string>>;　// string
```

## [898.Includes](https://github.com/type-challenges/type-challenges/blob/main/questions/00898-easy-includes/README.md)
```ts
// ============= Test Cases =============
import type { Equal, Expect } from './test-utils'
type cases = [
Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,
Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,
Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,
Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,
Expect<Equal<Includes<[1, 2, 3], 2>, true>>,
Expect<Equal<Includes<[1, 2, 3], 1>, true>>,
Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
Expect<Equal<Includes<[false, 2, 3, 5, 6, 7], false>, true>>,
Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
Expect<Equal<Includes<[1], 1 | 2>, false>>,
Expect<Equal<Includes<[1 | 2], 1>, false>>,
Expect<Equal<Includes<[null], undefined>, false>>,
Expect<Equal<Includes<[undefined], null>, false>>,
]
// ============= Answer =============
type Includes<T extends readonly any[], U> = T extends [infer F, ...infer R] ?
	Equal<U, F> extends true ? 
		true 
		: Includes<R, U> 
	: false;
```
### 解答アプルーチ
 `Include`型は、ある型が配列にあるかどうかチェックする方法です。
 1. 条件型：指定した型`T`は配列`T`の中で存在かどうかをチェックすると、条件型を使います。
 2. 配列分割:条件型おいて`T`が`[infer F, ...infer R]`の形状にマッチするかどうかでタプルを分解します。ここで、`F`はタプルの最初のヘッドで、`infer R`はそれ以外の要素を意味します。
 3. `Equal`：現在調べて要素`F`と`U`を比較するために、`Equal<F, U>`を使います。もしそれが`true`を拡張する場合、マッチが見つかったということで、最終に`true`を返します。
 4. 再帰：マッチ見つかれない場合は、タプルの残り部分は同じチェックを繰り返します。リストの次のアイテムをチェックして続け、マッチが見つかるか全てのアイテムを検索したまで続けます。
 5. ベースケース：もし`T`が`[infer F, infer R]`の構造とマッチしない場合、検索待ちアイテムがなくなったことを意味します。つまり全部の要素をチェックし終わったら、`U`がタプル内部で見つからなかったので、`false`を返します。