[Typescript] typeof "object value" [keyof typeof "object value"] の動作を丁寧に解説してみる

## 初めに
[[Typescript] 「なぜ enum の利用が推奨されないのか？」をまとめてみた](https://qiita.com/saba_can00/items/696baa5337eb10c37342) の記事でTypescriptでは、enumを利用したいシーンでは「Union typeを利用しよう」と紹介させていただきました。

その際にUnion typeとして表現する際に登場した``` typeof "object value" [keyof typeof "object value"] ```という表現が慣れていないと何をやっているのかわからないので、本記事では``` typeof "object value" [keyof typeof "object value"]``` の動作について丁寧に説明させていただきます。

本記事のコードの検証には、[Typescript Playground(v4.1.3)](https://www.typescriptlang.org/play?) を利用しています。


```typescript

const ACTIVITY_LEVEL = {
  LITTLE : 1.2,
  LIGHT: 1.375,
  MODERATE: 1.55, 
  HEAVY: 1.725,
  VERY_HEAVY: 1.9
} as const

// ↓ここの挙動について本記事では解説します
type ActivityLevel = typeof ACTIVITY_LEVEL[keyof typeof ACTIVITY_LEVEL]
```

## 各要素に分解してみる
``` typeof "object value" [keyof typeof "object value"]```は大きく3つの要素で構成されています。

1. オブジェクトの型を取得する ```typeof operator``` 
2. 型のプロパティ名のUnionTypeを表現する ```keyof operator```
3. ある型のプロパティの型を表現できる```T[K]```

この時点では、「この人は何言っているの？」と言う方もいらっしゃるとは思いますが、このあと詳細に解説させていただきますので、お付き合いください。

### 1.オブジェクトの型を取得する ```typeof operator``` 
まずは```typeof operator```についてです。
```typeof operator```を利用すると下記のように特定の値の型を取得することができます。

```typescript
let test1 = 'test1'
//↓は'string'に'number'を代入しているので、コンパイルエラーになる
let test2: typeof test1 = 2 
```

'string'や'number'などの基本型に利用するとわかりやすいのですが、これを具体的なオブジェクトの値に利用すると得られる型は少し複雑になります。

```typescript
let person = {
  age: 30,
  name: 'Taro'
}

// typeof personは'{ age: number; name: string; }'型を返す
let personOK: typeof person = {
  age: 10,
  name: 'Jiro',
} 
// nameがないのでエラー
let personNG: typeof person = {
  age: 20,
}
```

### 2. 型のプロパティ名のUnionTypeを表現する ```keyof operator```
次は```keyof operator```について解説します・
```keyof operator```は少しわかりにくいのですが、ある型Tのプロパティ名のUnionTypeを取得する演算子で、```index type query```と言われています。

具体例をみた方がわかりやすいので、下記に例を記載します。

```typescript

interface Person {
  name: string;
  age: number;
  location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...
```
K1はPersonのプロパティである ```name```,```age```,```location```のUnionType
K2はArrayのプロパティである```length```,```push```,```pop```・・・(以下略) のUnionType
となります。


### 3. ある型のプロパティの型を表現できる```T[K]```
```T[K]```は```indexed access types```、または```lookup types```と呼ばれ、オブジェクトのプロパティにアクセスするような記法でプロパティの型を取得できます。
 
```T[K]```の```K```には先ほど説明した```keyof operator```で取得したUnionTypeやstring, numberが利用できます。

```keyof operator```の例でも利用した```Person```を利用して、具体例を示します。

```typescript

interface Person {
  name: string;
  age: number;
  location: string;
}

type P1 = Person["name"] // string
type P2 = Person["name" | "age"] // string | number
type P3 = Person[keyof Person]// string | number
```

ここでP3に注目していただきたいのですが、P3では下記のように型の解釈がされます。

1. ``` keyof Person = "name" | "age" | "location" ```
2. ``` Person[keyof Person] = Person["name" | "age" | "location"] ```
3. ``` Person[keyof Person] = string | number ```

## 再度、``` typeof "object value" [keyof typeof "object value"]```を確認

最初に記載したenumの代わりとして利用するUnionTypeの表現をもう一度、見てみましょう。
わかりやすくするために、```TypeOfActivityLevel```という型を追記しています。

```typescript

const ACTIVITY_LEVEL = {
  LITTLE : 1.2,
  LIGHT: 1.375,
  MODERATE: 1.55, 
  HEAVY: 1.725,
  VERY_HEAVY: 1.9
} as const

type TypeOfActivityLevel = typeof ACTIVITY_LEVEL
type ActivityLevel = TypeOfActivityLevel[keyof TypeOfActivityLevel]

```

ここの```type ActivityLevel```は先程のPersonの例と同じように次のように解釈されます。

```typescript

//STEP1
type TypeOfActivityLevel = { 
    readonly LITTLE: 1.2; 
    readonly LIGHT: 1.375; 
    readonly MODERATE: 1.55;
    readonly HEAVY: 1.725;
    readonly VERY_HEAVY: 1.9; 
} 

//STEP2
keyof TypeOfActivityLevel = "LITTLE" | "LIGHT" | "MODERATE" | "HEAVY" | "VERY_HEAVY" 

//STEP3
type ActivityLevel = 1.2 | 1.375 | 1.55 | 1.725 | 1.9 
```

このようにみていくと、``` typeof ACTIVITY_LEVEL[keyof typeof ACTIVITY_LEVEL]```みたいな表現も理解しやすくなるかと思います。

### おわりに
自分が初見で見た時に「これは何をやっているんだ」と悩んだので、他の方の助けになればと思いまとめました。
もしどなたかの助けになれば幸いです。

それでは、楽しいTypescript Lifeを

## 参考リンク
* [TypeScript 2.1のkeyofとかMapped typesがアツい](https://qiita.com/Quramy/items/e27a7756170d06bef22a)
* [(TypeScript Documentation) TypeScript 2.1:keyof and Lookup Types](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types)
* [Static types for dynamically named properties](https://github.com/microsoft/TypeScript/pull/11929#issue-91466667)
* [(TypeScript Documentation) The typeof type operator](https://www.typescriptlang.org/docs/handbook/2/typeof-types.html#the-typeof-type-operator)