りかいが深まる TypeScript の動的型付け：in と keyof の実践的活用法

## はじめに
オブジェクトのkeyを動的に設定するための書き方として、インデックスシグネチャというものがあります。
そのインデックスシグネチャを定義する際、以下のような書き方があることを知りました。
```typescript
type User<T> = {
  [k in keyof T]: string;
};
```

この記事では`in`と`typeof`のそれぞれについて触れ、上記の型が表す意味を解説します。

## inの役割
`in`はインデックスシグネチャに使用される構文で、Mapped Typesと呼ばれる型を構築します。

### インデックスシグネチャ
そもそもインデックスシグネチャとはオブジェクトのkeyをあらかじめ宣言することなく、動的にあとから追加できるようにするものです。

```typescript:インデックスシグネチャのおさらい
type User = {
  [k: string]: string;
};

const useData: User = {
  name: "Kevin",
  country: "America",
};
```
`User`型は`string`型のkeyと`string`型の値を持つオブジェクトとして定義しています。
そして`userData`は`User`型となっており、型の定義に合致するプロパティであれば自由に追加ができます。

しかし、あまりにも自由に追加されるのも困ることがあります。
ある程度制限をかけたいときに使えるのが`in`です。

### 基本のin

```typescript:inの基本
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

type ApiProcess = {
  [k in HttpMethod]: () => void;
};

const apiProcess: ApiProcess = {
  GET: () => {
    console.log("get");
  },
  POST: () => {
    console.log("post");
  },
  PUT: () => {
    console.log("put");
  },
  DELETE: () => {
    console.log("delete");
  },
};
```
まず、`HttpMethod`というユニオン型を用意しました。

そして、インデックスシグネチャの中で、`in`のあとにユニオン型を指定しています。

これにより、`ApiProcess`という型は`HttpMethod`に定義された4つの値をキーに持つオブジェクトであるというふうに定義できました。

`in`を使わない場合、`ApiProcess`は以下のように書く必要があります。
```typescript
type ApiProcess = {
  GET: () => void;
  POST: () => void;
  PUT: () => void;
  DELETE: () => void;
};
```
同じようなことを繰り返し書いているので、冗長に見えます。

`in`を使うことで、設定したいkeyを制限しつつ、同じことを繰り返し書かずともオブジェクトの型を定義できます。

### その他のin
上記で確認したのはMapped TypesというTypeScriptにおける`in`でしたが、他にも`in`を使った構文は存在します。

ややこしいので、念の為整理しておきます。

#### for...in
ループ処理で使用する構文です。

```javascript
const obj = { a: 1, b: 2, c: 3 };

for (const key in obj) {
    console.log(`${key}: ${obj[key]}`);
}
```

#### 判定のin
指定した値がオブジェクトのプロパティに存在するかどうかを判定します。

```javascript
const animal = {
  dog: "わんわん",
  cat: "にゃーにゃー",
};
if ("dog" in animal) {
  console.log(animal.dog);
}
```

これらはもともとJavaScriptにも用意されている構文です。
今回確認したMapped Typesとは関係がないので注意してください。

## keyofの役割
[過去の記事](https://qiita.com/course_k/items/e0fb2146aa8064328729#%E5%AF%BE%E5%87%A6%E6%96%B9%E6%B3%95)で一度`keyof`については触れていますが、改めて確認します。

`keyof`演算子はオブジェクトのプロパティだけをまとめて取得して、リテラルのユニオン型として設定することができます。

```typescript:keyofの基本
type Person = {
    name: string;
    age: number;
    address: string;
}

type PersonKeys = keyof Person;
// type PersonsKeys = 'name' | 'age' | 'address'と定義するのと同じ
```

これにより、オブジェクトのプロパティを動的に取得することができます。
```typescript:keyofの使用例
type Person = {
  name: string;
  age: number;
  address: string;
};

const user: Person = {
  name: "Kevin",
  age: 28,
  address: "address",
};

const printPerson = (param: keyof Person) => {
  console.log(user[param]);
};
```

## in keyof Tの意味
ここまでくれば、何を表しているかがわかるかと思います。

`in`はユニオン型をもとにオブジェクトのkeyを決定します。
そして、`keyof`はオブジェクトのプロパティからユニオン型を生成します。

つまり、あるオブジェクトのプロパティと同じkeyを持つことを指定できる書き方が`in keyof T`になります。

### in keyof Tの使用例

うまく使うことで、以下のような動的なバリデーションの処理を作成することができます。
```typescript
interface FormData {
  username: string;
  age: number;
  email: string;
  password: string;
}

// バリデーションルール用の型を定義
// Tのプロパティの型をアロー関数の引数の型として設定
type ValidationRules<T> = {
  [k in keyof T]: (value: T[k]) => string | null;
};

// バリデーションルールのオブジェクトを作成
// バリデーションルール用の型のTにはFormDataが設定される
const validationRules: ValidationRules<FormData> = {
  username: (value) =>
    value.length < 3 ? "ユーザー名は3文字以上である必要があります" : null,
  age: (value) => (value > 100 ? "年齢は100未満で入力してください" : null),
  email: (value) =>
    /@/.test(value) ? null : "有効なメールアドレスを入力してください",
  password: (value) =>
    value.length < 8 ? "パスワードは8文字以上である必要があります" : null,
};

// フォームにバリデーションを実施する処理
// 戻り値の型や、エラー情報の型にもin keyofを使用
function validateForm<T>(
  data: T,
  rules: ValidationRules<T>
): { [k in keyof T]?: string } {
  const errors: Partial<{ [k in keyof T]: string }> = {};

  // ここでのinはループ。Mapped Typesではない
  for (const key in data) {
    if (key in rules) {
      const error = rules[key](data[key]);
      if (error) {
        errors[key] = error;
      }
    }
  }

  return errors;
}
```

## まとめ
`in keyof`を使うことで、型安全を保ちながら柔軟な処理を書くことができました。
一見よくわからない処理でも、一つずつ紐解いていくことで何をしているのかを明らかにすることができます。

もっと型に関連する知識を沢山身につけていきたいと思いました。