TypeScriptの『typeof X[keyof typeof X]』の意味を順を追って理解する

JavaScript

TypeScriptのコードでたまにみかけるtype HogeType = typeof Hoge[kyof typeof Hoge]の意味について解説をします。

結論: typeof X[kyeof typeof X]の意味

typeof X[kyeof typeof X]オブジェクトXのプロパティの型をUnion型で表現します。
具体例は以下の通りです。

const COLOR = {
  RED: "red",
  GREEN: "green",
  BLUE: "blue",
} as const;

type Color = typeof COLOR[keyof typeof COLOR];
// 以下のように評価される
// type Color = "red" | "green" | "blue"

特に、Enum型の代わりにUnion型を利用する際にtypeof X[kyeof typeof X]の表現がよく使われます。
Enum型とUnion型の比較は【TypeScript】Enum型からUnion型に書き換える方法と具体例で紹介しています。

typeof X[kyeof typeof X]で利用される演算子や型について

typeof X[kyeof typeof X]を使う場面では以下の演算子や型が利用されます。

  • const assertion
  • keyof
  • typeof
  • Lookup Types(ルックアップ型)

以下ではそれぞれについて解説をします。

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

const asssertion(constアサーション)とはオブジェクトや値をreadonly(再代入不可)にする機能です。
const assertionはreadonlyにしたい変数の末尾にas constをつけることで実現できます。

const asssertionの具体例は以下の通りです。

let arr = [10, 20] as const;
// 以下のように型推論される
// let arr: readonly [10, 20]

let obj = { text: "Hello" } as const;
// 以下のように型推論される
// let obj: {
//     readonly text: "Hello";
// }

// ネストしたオブジェクトの場合、再帰的にreadonlyが付与される
let taro = {
  firstName: 'Taro',
  lastName: 'Suzuki',
  birthplace: {
    country: 'Japan',
    prefecture: 'Tokyo'
  }
} as const
// 以下のように型推論される
// let taro: {
//     readonly firstName: "Taro";
//     readonly lastName: "Suzuki";
//     readonly birthplace: {
//         readonly country: "Japan";
//         readonly prefecture: "Tokyo";
//     };
// }

const assertionの利用した場合としない場合の比較は以下の通りです。

// const assertionを利用した場合
const x = { a: 1 } as const;
x.a = 3 // error

// const assertionを利用しない場合
const x = { a: 1 };
x.a = 3
console.log(x.a) // 3

keyof(index type query operator)について

keyof(index type query operator)はオブジェクトの型を受け取り、オブジェクトのプロパティで構成されたUnion型を返す演算子です。

keyofの具体例は以下の通りです。

type Person = {
  firstName: string;
  lastName: string;
};

type PersonPropertyType = keyof Person;
// 'firstName' | 'lastName'

// OK
const firstName: PersonPropertyType = "firstName";

// OK
const lastName: PersonPropertyType = "lastName";

// NG
const age: PersonPropertyType = "age";
// Type '"age"' is not assignable to type 'key

typeofについて

typeofはオペランド(被演算子、演算対象の変数のこと)の型を文字列として返す演算子です。typeof 変数を型にすると、変数の型定義が利用できます。

typeofの具体例は以下の通りです。

// プリミティブ型の場合

const hello: string = "Hello";

console.log(typeof hello);
// string

const hi: typeof hello = "Hi";
// 以下のように型推論される
// const hi: string
// オブジェクトの場合

const taro = {
  name: "Taro",
  age: 20,
};

console.log(typeof taro);
// object

const jiro: typeof taro = {
  name: "Jiro",
  age: 22,
};
// 以下のように型推論される
// const jiro: {
//     name: string;
//     age: number;
// }

Lookup Types(ルックアップ型)について

Lookup Types(ルックアップ型)とはオブジェクトのプロパティの型を表現するものです。
Lookup Typesは『indexed access operator』とも呼ばれています。1

Lookup TypesはT[K]のように記述します。T[K]は『型Tにおけるキー(プロパティ)Kの型』を表現します。

Lookup Typesの具体例は以下の通りです。

type Person = {
  firstName: string;
  lastName: string;
};

// Person型のfirstNameキーの型
type FirstNameType = Person["firstName"];
// 以下のように型推論される
// type FirstNameType = string

// Person型のlastNameキーの型
type LastNameType = Person["lastName"];
// 以下のように型推論される
// type LastNameType = string

typeof X[kyeof typeof X]を要素分解することで意味を理解する

以下のサンプルコードのtypeof COLOR[keyof typeof COLOR]"red" | "green" | "blue"というUnion型になります。

const COLOR = {
  RED: "red",
  GREEN: "green",
  BLUE: "blue",
} as const;

type Color = typeof COLOR[keyof typeof COLOR];
// 以下のように評価される
// type Color = "red" | "green" | "blue"

type Color = typeof COLOR[keyof typeof COLOR];の意味について、以下の手順で説明します。

  1. 『typeof X』について
  2. 『keyof typeof X』について
  3. 『typeof X[keyof typeof X]』について

typeof X: オブジェクトを型として定義する

typeof COLORはCOLORオブジェクトを型として定義します。

const COLOR = {
  RED: "red",
  GREEN: "green",
  BLUE: "blue",
} as const;
// 以下のように評価される
// const COLOR: {
//     readonly RED: "red";
//     readonly GREEN: "green";
//     readonly BLUE: "blue";
// }

type TypeOfColor = typeof COLOR
// type ColorType = {
//     readonly RED: "red";
//     readonly GREEN: "green";
//     readonly BLUE: "blue";
// }

keyof typeof X: プロパティをUnion型で表現する

keyof typeof COLORtypeof COLOR、つまりCOLOR型のプロパティをUnion型で定義します。

const COLOR = {
  RED: "red",
  GREEN: "green",
  BLUE: "blue",
} as const;

type TypeOfColor = typeof COLOR
// 以下のように評価される
// type ColorType = {
//     readonly RED: "red";
//     readonly GREEN: "green";
//     readonly BLUE: "blue";
// }

type KeyofTypeofColor= keyof typeof COLOR
// 以下のように評価される
// type KeyofTypeofColor = "RED" | "GREEN" | "BLUE"

typeof X[keyof typeof X]: プロパティの型をUnion型で表現する

typeof COLORCOLOR型を意味します。
ですので、typeof COLOR[keyof typeof COLOR]COLOR型のkeyof typeof COLOR、つまり"RED" | "GREEN" | "BLUE"プロパティの型を取得します。

const COLOR = {
  RED: "red",
  GREEN: "green",
  BLUE: "blue",
} as const;

type KeyofTypeofColor = keyof typeof COLOR
// 以下のように評価される
// type KeyofTypeofColor = "RED" | "GREEN" | "BLUE"

type Color = typeof COLOR[keyof typeof COLOR]
// 以下のように評価される
// type Color = "red" | "green" | "blue"

以上の結果、typeof COLOR[keyof typeof COLOR]"red" | "green" | "blue"というUnion型として推論されます。

typeof X[kyeof typeof X]を利用した具体例

showColorは引数のColor型の値に応じて動的なログを出力するメソッドです。

const COLOR = {
  RED: "red",
  GREEN: "green",
  BLUE: "blue",
} as const;

type Color = typeof COLOR[keyof typeof COLOR];
// type Color = "red" | "green" | "blue"

const showColor = (color: Color) => {
  if (color === COLOR.RED) {
    console.log("赤色です");
  }
  if (color === COLOR.GREEN) {
    console.log("緑色です");
  }
  if (color === COLOR.BLUE) {
    console.log("青色です");
  }
};

showColor(COLOR.RED);
// 赤色です

showColor(COLOR.GREEN);
// 緑色です

showColor(COLOR.BLUE);
// 青色です

// OK
// 文字列リテラルを直接指定してもOK
showColor("red");
// 赤色です

// NG
// Union型に含まれてない
showColor("yellow");
// => Argument of type '"yellow"' is not assignable to parameter of type 'Color'.(2345)

まとめ

今回のまとめ
  • typeof X[kyeof typeof X]はオブジェクトXのプロパティの型をUnion型で表現する
  • as constはオブジェクトや値をreadonlyにする場合に利用する
  • keyofはオブジェクトの型を受け取り、オブジェクトのプロパティで構成されたUnion型を返す
  • typeofはオペランドの型を文字列として返す
  • T[K]は型Tにおけるキー(プロパティ)Kの型を表現する

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考