keyof と typeof をすこ〜し細かく解説！後輩の質問から得たTypeScriptの知識

こんにちはゆせです！先日後輩からkeyof, typeofについて結構聞かれて、即答できない部分があったのでここにアウトプットしておきます！！後輩ちゃんありがとう笑
## 本記事で解像度が高くなるであろうものたち
- [keyof](https://typescriptbook.jp/reference/type-reuse/keyof-type-operator)
- [typeof](https://typescriptbook.jp/reference/type-reuse/typeof-type-operator)
- [extends(ここで言うexntendsは継承のことを指します)](https://typescriptbook.jp/reference/object-oriented/interface/interface-inheritance)
- [ユニオン型](https://typescriptbook.jp/reference/values-types-variables/union)
- [タプル型](https://typescriptbook.jp/reference/values-types-variables/tuple)
- [インデックスアクセス型](https://typescriptbook.jp/reference/type-reuse/indexed-access-types)
- [constアサーション](https://typescriptbook.jp/reference/values-types-variables/const-assertion)

上記はすべて[サバイバルTypeScript](https://typescriptbook.jp/)に飛ぶようにしています。ではさっそくコード含めて見ていきましょう！

## keyofについて
```typescript:keyof.ts
type TMember = {
  name: string,
  grade: number,
};

type TMemberKey = keyof TMember // TMemberKey =  "name" | "grade"
```
`keyof`について簡単にまとめます
- オブジェクトのプロパティ名(key-valueの`key`)を取得できる
- [ユニオン型](https://typescriptbook.jp/reference/values-types-variables/union)で取得できる
- 「**型**」に対して使用できる(「変数」には使用できない)

上記のコードだとあまりメリットが湧かないかもですが、[サバイバルTypeScript](https://typescriptbook.jp/reference/type-reuse/keyof-type-operator)にはこう書かれています。
> プロパティが何十個もあるようなオブジェクトを想像してみてください。そのプロパティ名のユニオン型を定義する必要が出てきたとします。その際に、プロパティ名をすべて転記するとなると、転記漏れや書き間違いもあるでしょう。そういう場合はkeyofを使うとそもそも書き写す必要がないため、便利な上に安全なコーディングができます。

確かに納得です。`TMember`には上記の通りプロパティは2つだけですが、仮にこれが20個あるとして、これをユニオン型で定義しないといけないとなった時、保守性、可読性の観点からすると`keyof`は便利ですね。`keyof`の一撃必殺で20個のユニオン型が出来上がります。

### 実際に使われるケース
```typescript:keyof.ts
type TMember = {
  name: string,
  grade: number,
};

type TMemberSurvey = {
  techSkill: number,
  communicationSkill: number,
  techLanguage: string,
};

type TFormTitle = keyof TMember | keyof TMemberSurvey;
// TFormTitle = "name" | "grade" | "techSkill" | "communicationSkill" | "techLanguage"

type TFormInput = "text" | "number";

type TForm = {
  title: TFormTitle, 
  type: TFormInput,
  // titleはname, grade, techSkill, communicationSkill, techLanguageに限定される
  // typeはtextかnumberに限定される
};

const form_v1: TForm = {
  title: "techSkill",
  type: "text",
};

// もしくは
const form_v2: TForm[] = [
  { title: "name", type: "text" },
  { title: "grade", type: "number" },
  { title: "techSkill", type: "number" },
  { title: "communicationSkill", type: "number" },
  { title: "techLanguage", type: "text" },
  // { title: "apple", type: "text" }, ←titleにappleは含まれていないのでコンパイルエラー
];
```
一見上記のコードは色々書いてしまっているので複雑に見えますが、上からちゃんと読んでみると案外簡単なものです。

## typeofについて
``` typescript:typeof.ts
const member = {
  name: "太郎",
  age: 21,
};

type TMember = typeof member;
// TMember = {name: string; age: number;}
```
`typeof`について簡単にまとめます
- JavaScriptの`typeof演算子`とは別物
- 宣言済みの「変数」の型を取得できる
  - 「**変数**」に対して使用できる(「型」には使用できない)

一言で表すと、変数から**型を抽出する**型演算子です。変数の値が`true`であれば`boolean`を取得してくれるということです。
### 実際に使われるケース
```typescript:typeof.ts
const language = ["JavaScript", "TypeScript", "Python", "Java", "C#"] as const;
type TLanguage = (typeof language)[number];
// TLanguage = "JavaScript"  "TypeScript" | "Python" | "Java" | "C#"

type TMember = {
  name: string,
  language: TLanguage,
};

const member: TMember = {
  name: "ゆせ",
  language: "TypeScript", //TLanguageにないものはコンパイルエラー
};
```
### 後輩ちゃんに解説してあげられなかったやつ！
> const language = ["JavaScript", "TypeScript", "Python", "Java", "C#"] as const;
type TLanguage = (typeof language)[number];

この2行を後輩に聞かれ即答できませんでした。。いつも感覚で読んで感覚でコードを書いているのを反省しています😇解説します！
### constアサーション「as const」
`readonly`（読み取り専用、変更不可)の[タプル型](https://typescriptbook.jp/reference/values-types-variables/tuple#%E3%82%BF%E3%83%97%E3%83%AB%E3%81%AE%E5%9E%8B)で取得できます。そもそも今回の目的は`language`の値(`"JavaScript", "TypeScript"...`)自体を型に導く(つまりユニオン型にする)ためのファーストステップとして`as const`を使っています。仮に使わないとなると、型推論で以下のようにただの`string`型の配列とみなされてしまいます。
```typescript:as const.ts
const arrLanguage = ["JavaScript", "TypeScript", "Python", "Java", "C#"];
// arrLanguageの型 = string[]
const language = ["JavaScript", "TypeScript", "Python", "Java", "C#"] as const;
// languageの型 = readonly ["JavaScript", "TypeScript", "Python", "Java", "C#"]
```
### タプル型とインデックスアクセス型
さて、では上で取得した`readOnly`のタプル型`language`をユニオン型(`TLanguage = "JavaScript" | "TypeScript" | "Python" | "Java" | "C#"`)で取得したいです。
``` typescript:indexAccess.ts
type TLanguage = (typeof language)[number];
```
答えは上記の通りなのですが、パッとわかりにくいので一つずつ見ていきましょう。
1. `(typeof language)`
変数`language`の型を取得します。つまり上でやった通り、`readonly ["JavaScript" | "TypeScript" | "Python" | "Java" | "C#"]`の取得になります。constアサーションのおかげでこの型を取得できています👍
2. `[number]`
なんやこれ？と思った人もいるでしょう。タプルに対して `[number]` を使うと、全てのインデックスの要素の型をユニオン型で取得します。
つまりは、超わかりやすく書くと

`language[0]` の型：`"JavaScript"`
`language[1]` の型：`"TypeScript"`
`language[2]` の型：`"Python"`
`language[3]` の型：`"Java"`
`language[4]` の型：`"C#"`

結果、
```typescript:language.ts
type TLanguage = (typeof language)[number];
// TLanguage = "JavaScript" | "TypeScript" | "Python" | "Java" | "C#"
```
となるわけです！


タプル型とインデックスアクセス型については[こちら](https://typescriptbook.jp/reference/type-reuse/indexed-access-types#%E3%82%BF%E3%83%97%E3%83%AB%E5%9E%8B%E3%81%A8%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%9E%8B)

### extends君
```typescript:extends.ts
const member = {
  name: "太郎",
  age: 21,
}
type TMember = typeof member;

interface TUpdateMember extends TMember {
  grade: number;
}
/*
TUpdateMember = {
  name: string,
  age: number,
  grade: number,
}
*/

// type定義はextends君使えないからこれ↓↓↓
type TUpdateMember = TMember & {
  grade: number;
};
```
extendとは英語で「拡大する、伸ばす」という意味がありますが、typescriptでも同じように型を拡張しているイメージですかね。`extends`は`interface`定義で使用することができ、`type`定義であれば`&`を使います。

## keyof, typeof一緒に使うパターンもあるぞおお
### 実際に使われるケースその1
```typescript:keyof&&typeof.ts
const member = {
  name: "ゆせ",
  age: 21,
  language: "TypeScript",
};

type TMember = keyof typeof member;
// TMember = "name" | "age" | "language"
```
今回やりたいことが、「**変数のプロパティ名の取得**」だとします。つまり、nameやageを取得したいということです。
おさらいになりますが、プロパティ名の取得は`keyof`でできるものの、型にのみ使用可能でしたね。変数には使用できません。memberは変数なので、直接`keyof`のみで取得することはできません。

じゃあどうすればいいかと言うと、変数`member`の型を取得し、それを`keyof`に当ててあげればいいわけです！イメージ↓↓
1. `typeof member`でmemberの型を取得
`name:string, age:number, ...`といった型を取得できます。
2. 1で取得した型を`keyof`に当てる
そうすれば、`name`, `age`, `language`を取得できますね！

1と2を合体させたコードが
```typescript:keyof&&typeof.ts
type TMember = keyof typeof member;
```
になります！英語や普段のプログラミングと同じで、直感的にわからなければ分解していくと少しわかりやすいですね！次はもう少し複雑です
### 実際に使われるケースその2
```typescript:keyof&&typeof.ts
const member = {
  name: "太郎",
  age: 21,
  language: "TypeScript",
} as const;

type TMember = (typeof member)[keyof typeof member];
// TMember = "太郎" | 21 | "TypeScript"
```
いかがでしょう。今まで解説した通りにちゃんと考えることができれば`TMember`の実態がわかるかと思いますが、とは言っても直感的にはわかりにくいものなので解説していきます。先ほどと同様、分解して見ていきましょう！

1. ここでの`typeof member`
memberには`as const`がついていますね。読み取り専用`readOnly`になります。先ほどは配列の中身をそのままで取得していましたが、今回はどうでしょう。以下がその結果です。
```typescript:keyof&&typeof.ts
const member = {
  name: "太郎",
  age: 21,
  language: "TypeScript",
} as const;

/* ↓memberの型を見てみると、、
member = {
 readonly name: "太郎";
 readonly age: 21;
 readonly language: "TypeScript";
}

as constがなければ、、
member = {
 name: string;
 age: number;
 language: string;
}
*/
```
今回も同様、そのまま読み取り専用で取得していますね。constアサーションがついたmemberの型は、memberそのものを型としてるのです。`as const`がなければ、上記の通りそれぞれの型をプリミティブに取得します。

２. `[keyof typeof member]`を忘れずに
先ほど上でやったことと全く同じです。一言で言うと、変数の中のプロパティ名を取得します。つまりここで言う、name, age等の取得をしています。

３. 1と2でやったことを合体しよう！`(typeof member)[keyof typeof member]`
この形`(typeof member)[keyof typeof member]　≒　(型[キー])`はインデックスアクセス型です。全てのインデックスの要素の型をユニオン型で取得します。結論から見た方が早いのでわかりやすく書きます。

`member[name]` → `"太郎"`
`member[age]` → `"21"`
`member[language]` → `"TypeScript"`

`member`は読み取り専用そのものの型です。これは1でやったことです。
それにインデックス番号をつけてあげたく、今回はインデックス番号というの名の、プロパティ名を指定してあげるとその値を取得できるということですね。プロパティ名を取得し指定している部分(`[name]`とか)が2でやったことです。


インデックスアクセス型は特定の値を指定しない限りすべて取得するんでしたよね。今回はそれに該当するので、すべて取得し、結果
```typescript:keyof&&typeof.ts
const member = {
  name: "太郎",
  age: 21,
  language: "TypeScript",
} as const;

type TMember = (typeof member)[keyof typeof member];
// TMember = "太郎" | 21 | "TypeScript"
```
となります！

## まとめ
かなり細かく解説しているところもあり、文章が長くなってしまいました申し訳ないです🙇‍♂️

とにかくこの記事広範囲で`keyof`,`typeof`を使ったと思います！私が書いた解説を読みながら、「あれ？typeofってなんだっけ？」なんてこともあるかと思います。そんな時は初めに解説した部分に戻って都度確認しにいってください！
私もこの記事を書く前よりkeyofとtypeofの解像度が高まりました、考える、触れる反復がとにかく大事だと実感できました。

この記事が誰かのお役に立てれば嬉しいです！