LoginSignup
43

More than 1 year has passed since last update.

posted at

updated at

Organization

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

初めに

[Typescript] 「なぜ enum の利用が推奨されないのか?」をまとめてみた の記事で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) を利用しています。


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を利用すると下記のように特定の値の型を取得することができます。

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

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

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と言われています。

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


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を利用して、具体例を示します。


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という型を追記しています。


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の例と同じように次のように解釈されます。


//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を

参考リンク

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
43