【TypeScript】keyofとtypeofよく忘れるのでまとめ

TypeScriptで書いていると、「あれ、この場合はkeyofだったけ？typeofだったけ？」と最初の頃はよく忘れていたので備忘録的にまとめてみました。

# keyof
はじめに `keyof` です。

https://www.typescriptlang.org/docs/handbook/2/keyof-types.html

これは、**オブジェクト型のプロパティ名(key名)を取得** します。
そして、 **「型」に対して使用** します。

```typescript:例
/*
keyofを使用するとオブジェクトのプロパティ名をString Literal Union Typesで取得できる。
プロパティが数値の場合は、Numeric Literal Union Typesが取得される。
「型」に対して使用できる
*/

type SomeType = {
	foo: string;
	bar: string;
	baz: number;
}

const someKey: keyof SomeType; // someKey: 'foo' | 'bar' | 'baz'
```

# keyofの使用ケース
使用ケースとしては、 **オブジェクトのプロパティ名で新たにオブジェクトの型を作りたい** 時などに使用します。

```typescript
export type PersonalData = {
  name: string;
  age: number;
};

export type SurveyData = {
  health: string;
  motivation: string;
  remarks: string;
};

export type InputType = 'text' | 'number';
export type FormTitle = keyof PersonalData | keyof SurveyData;
//FromTitle: "name" | "age" | "health" | "motivation" | "remarks"

export type FormItem = {
  title: FormTitle;
  type: InputType;
};

const formItem: FormItem = {
    title: 'name',
    type: 'text'
};
```

# typeof
続いて、 `typeof` です。

https://www.typescriptlang.org/docs/handbook/2/typeof-types.html

これは、 **実際の値を型に変換** します。
**「変数」に対して使用** します。

```typescript:例
/*
JavaScriptのtypeof演算子とは別物 if (typeof n === "string")
宣言済みの「変数」の型を取得できる
「変数」に対して使用できる
型推論と組み合わせた時が有効
*/

const someObj = {
    foo: ''
}

const obj1: typeof someObj = { fo: ''} // error
const obj2: typeof someObj = { foo: ''} // not error
obj2.foo = 1 // error

// 初期値を入れない場合 
const obj!: typeof someObj;
```

# typeofの使用ケース
typeofで使用が考えられるケースとしては、 `配列の値をString Literal Typesとして使う` 時などです。

```typescript:例
const size = ['small', 'medium', 'large'] as const; // readonly ["small", "medium", "large"]
type Size = typeof size[number]; // 'small' | 'medium' | 'large'
```

それと、通常は interface を作ってから変数を宣言すると思いますが、
もし定義済みの変数をそのまま型として使用したいケースがあれば便利です。

```typescript
const someObject = {
  name: 'Tom',
  age: 20,
};

type SomeObject = typeof someObject; // SomeObject: {name: string, age: number}

interface ExtendedSomeObject extends SomeObject {
    address: string;
}; // SomeObject: {name: string, age: number, address: string}

const hoge: ExtendedSomeObject = {
    name: 'Jhon',
    age: 30,
    address: 'New York'
}
```

# keyofとtypeofの併用
続いては、`keyofとtypeofの併用` についてです。

keyof と typeof は併用することができます。

```typescript:例
/*
keyofとtypeofは併用が可能
keyof typeof をすることで、オブジェクトのプロパティ名のstringしか入力できない型安全になる
*/

const someObj1 = {
  foo: 'FOO',
  bar: 'BAR',
  baz: 'BAZ',
}

let obj1: keyof typeof someObj1; // obj1 = 'foo' | 'bar' | 'baz'
obj1 = 'foo';

/*
併用する理由
keyofは「変数には使えない」
typeofだけだと「オブジェクトの型」として認識される
「変数」かつ「オブジェクトのプロパティ名」をunion型として定義したい時
*/

// keyof
const someObj2 = {
  foo: 'FOO',
  bar: 'BAR',
  baz: 'BAZ',
}

// keyofは「変数」には使えないためここでエラーになる
const obj2: keyof someObj2; //error

// typeof
const someObj3 = {
  foo: 'FOO',
  bar: 'BAR',
  baz: 'BAZ',
}

let obj3: typeof someObj3; //obj3: { foo: string; bar: string; baz: string; }
obj3!.foo = 'string'
```

# Objectの値からリテラル型にする
続いても「併用パターン」です。

今回のケースは「**Objの値からリテラル型にする**」ケースです。

`typeof Type[keyof typeof Type]` とします。

コードは以下のようになります。

```typescript
export const FormTypes = {
  personal: 'personal',
  survey: 'survey',
} as const;  // as const で中身がreadonlyになる、readonlyのプロパティを書き換えようとするとエラーになる

/**
 * type FormType = 'personal' | 'survey' と同じ
 * @see https://book.yyts.org/tips/generates-type-from-object-property
*/
export type FormType = typeof FormTypes[keyof typeof FormTypes];
export type EditedType = FormType | null;
```

## as const(const アサーション)について

https://typescript-jp.gitbook.io/deep-dive/type-system/type-assertion

さきほど少し登場した、`as const` ですが、
Objectや配列などに `as const` をつけると、以下と同等になります。

```typescript
export const FormTypes = {
  personal: 'personal',
  survey: 'survey',
} as const;

type formTypes = typeof FormTypes;

// 以下と同等
type formTypes = {
    readonly personal: "personal";
    readonly survey: "survey";
}
```

つまり、**readonlyにして読み取り専用** になります。

# keyofとtypeofのまとめ
最後にここまでの内容をまとめます。

```markdown
# keyof
	- 「オブジェクトのプロパティ名」をstringで取得できる
	- プロパティ名がnumberの時はnumberが取得される
	- 「型」のみしか使えない

# typeof
	- 宣言済みの「変数」の型を取得できる
	- 型推論と組み合わせが有効
	- 「変数」のみしか使えない

# 併用
	- 「変数」かつ「オブジェクトのプロパティ名」だけを入力できる型にしたい時
```

以上となります。
お読み頂き有難うございました。