type-challenges 中級編10: ReplaceKeys、RemoveIndexSignature、PercentageParser

# type-challenges 中級編10: ReplaceKeys、RemoveIndexSignature、PercentageParser

## はじめに

[GitHubのtype-challengesリポジトリ](https://github.com/type-challenges)は、TypeScript型システムの理解を深めるための型クイズ問題集です。高品質な型を作成することで、バグを減らし、プロジェクトの保守性を向上させることができます。今回は中級編から3つの問題に挑戦します。問題もなかなか難しくなってきているので踏ん張り時です。

### 1. ReplaceKeys

この課題では、ユニオン型内のキーを置換する型を実装します。型にキーが存在しない場合、その置換をスキップします。型は3つの引数を取ります。

#### 例

```typescript
type NodeA = {
  type: "A";
  name: string;
  flag: number;
};

type NodeB = {
  type: "B";
  id: number;
  flag: number;
};

type NodeC = {
  type: "C";
  name: string;
  flag: number;
};

type Nodes = NodeA | NodeB | NodeC;

type ReplacedNodes = ReplaceKeys<
  Nodes,
  "name" | "flag",
  { name: number; flag: string }
>; // {type: 'A', name: number, flag: string} | {type: 'B', id: number, flag: string} | {type: 'C', name: number, flag: string}

type ReplacedNotExistKeys = ReplaceKeys<Nodes, "name", { aa: number }>; // {type: 'A', name: never, flag: number} | NodeB | {type: 'C', name: never, flag: number}
```

#### 私の回答

```typescript
type ReplaceKeys<U, T, Y> = U extends U
  ? {
      [K in keyof U]: K extends T
        ? K extends keyof Y
          ? Y[K]
          : never
        : U[K];
    }
  : never;
```

#### 解き方

- ユニオン型 `U` を1つずつ取り出してそれぞれに対して処理します。
- 各キー `K` が置換対象のキー `T` に含まれているか確認します。
- 含まれている場合、そのキーが新しい型 `Y` に存在するなら `Y[K]` を使用し、存在しないなら `never` を使用します。
- 含まれていない場合は元の型 `U[K]` を使用します。

:::note info
今までのUnion型の分配法則、
```ts 
{
    [K in keyof U]: U[K];
}
```
の型など基本的なことが試されてますね。
:::


### 2. RemoveIndexSignature

この課題では、オブジェクト型からインデックスシグネチャを除外する型を実装します。

#### 例

```typescript
type Foo = {
  [key: string]: any;
  foo(): void;
};

type A = RemoveIndexSignature<Foo>; // expected { foo(): void }
```

今回はわからず答えを見てしまいました....

```typescript
type RemoveIndexSignature<T> = {
  [K in keyof T as K extends `${infer _}` ? K : never]: T[K];
};
```

#### 解き方

- `keyof T` でオブジェクト型 `T` のキーを取得します。
- 各キー `K` が文字列リテラル型にマッチするかをチェックします。
- 文字列リテラル型にマッチするキーのみを新しいオブジェクト型に残します。

:::note info
```ts
[K in keyof T as K extends `${infer _}` ? K : never]
```
の書き方で文字列リテラルのキーを取り出すことができるのですね、、、知らなかった
:::


### 3. PercentageParser

この課題では、文字列からパーセントの構造を解析する型を実装します。正規表現 `/^(\+|\-)?(\d*)?(\%)?$/` に従い、3つの部分にマッチさせます。構造は `[符号, 数字, 単位]` とします。キャプチャされなかった場合、デフォルトは空文字列です。

#### 例

```typescript
type PString1 = "";
type PString2 = "+85%";
type PString3 = "-85%";
type PString4 = "85%";
type PString5 = "85";

type R1 = PercentageParser<PString1>; // expected ['', '', '']
type R2 = PercentageParser<PString2>; // expected ["+", "85", "%"]
type R3 = PercentageParser<PString3>; // expected ["-", "85", "%"]
type R4 = PercentageParser<PString4>; // expected ["", "85", "%"]
type R5 = PercentageParser<PString5>; // expected ["", "85", ""]
```

#### 私の回答

```typescript
type CheckPrefix<T> = T extends "+" | "-" ? T : never;
type CheckSuffix<T> = T extends `${infer P}%` ? [P, "%"] : [T, ""];
type PercentageParser<A extends string> = A extends `${CheckPrefix<infer L>}${infer R}`
  ? [L, ...CheckSuffix<R>]
  : ["", ...CheckSuffix<A>];
```

#### 解き方

- `CheckPrefix` 型を使用して符号（`+` または `-`）をチェックします。
- `CheckSuffix` 型を使用してパーセント記号をチェックし、数字部分とパーセント記号を分離します。
- `PercentageParser` 型で、入力文字列 `A` が符号付きの形式にマッチするかを確認し、符号と残りの部分を解析します。
- 符号がない場合は、残りの部分だけを解析します。

:::note 
いつも通り文字列をゴネゴネするだけですね。
:::


### まとめ

このように、TypeScriptの型システムを理解し、実際に型を実装することで、より安全で保守性の高いコードを書くことができます。ぜひこれらの課題を解いて、型力UPしてみてください。

### 参考記事

- [TypeScript公式ドキュメント](https://www.typescriptlang.org/docs/)
- [type-challenges GitHubリポジトリ](https://github.com/type-challenges/type-challenges)