初めに
[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つの要素で構成されています。
- オブジェクトの型を取得する
typeof operator
- 型のプロパティ名のUnionTypeを表現する
keyof operator
- ある型のプロパティの型を表現できる
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では下記のように型の解釈がされます。
keyof Person = "name" | "age" | "location"
Person[keyof Person] = Person["name" | "age" | "location"]
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を