こんにちはゆせです!先日後輩からkeyof, typeofについて結構聞かれて、即答できない部分があったのでここにアウトプットしておきます!!後輩ちゃんありがとう笑
本記事で解像度が高くなるであろうものたち
上記はすべてサバイバルTypeScriptに飛ぶようにしています。ではさっそくコード含めて見ていきましょう!
keyofについて
type TMember = {
name: string,
grade: number,
};
type TMemberKey = keyof TMember // TMemberKey = "name" | "grade"
keyof
について簡単にまとめます
- オブジェクトのプロパティ名(key-valueの
key
)を取得できる - ユニオン型で取得できる
- 「型」に対して使用できる(「変数」には使用できない)
上記のコードだとあまりメリットが湧かないかもですが、サバイバルTypeScriptにはこう書かれています。
プロパティが何十個もあるようなオブジェクトを想像してみてください。そのプロパティ名のユニオン型を定義する必要が出てきたとします。その際に、プロパティ名をすべて転記するとなると、転記漏れや書き間違いもあるでしょう。そういう場合はkeyofを使うとそもそも書き写す必要がないため、便利な上に安全なコーディングができます。
確かに納得です。TMember
には上記の通りプロパティは2つだけですが、仮にこれが20個あるとして、これをユニオン型で定義しないといけないとなった時、保守性、可読性の観点からするとkeyof
は便利ですね。keyof
の一撃必殺で20個のユニオン型が出来上がります。
実際に使われるケース
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について
const member = {
name: "太郎",
age: 21,
};
type TMember = typeof member;
// TMember = {name: string; age: number;}
typeof
について簡単にまとめます
- JavaScriptの
typeof演算子
とは別物 - 宣言済みの「変数」の型を取得できる
- 「変数」に対して使用できる(「型」には使用できない)
一言で表すと、変数から型を抽出する型演算子です。変数の値がtrue
であればboolean
を取得してくれるということです。
実際に使われるケース
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
(読み取り専用、変更不可)のタプル型で取得できます。そもそも今回の目的はlanguage
の値("JavaScript", "TypeScript"...
)自体を型に導く(つまりユニオン型にする)ためのファーストステップとしてas const
を使っています。仮に使わないとなると、型推論で以下のようにただのstring
型の配列とみなされてしまいます。
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#"
)で取得したいです。
type TLanguage = (typeof language)[number];
答えは上記の通りなのですが、パッとわかりにくいので一つずつ見ていきましょう。
-
(typeof language)
変数language
の型を取得します。つまり上でやった通り、readonly ["JavaScript" | "TypeScript" | "Python" | "Java" | "C#"]
の取得になります。constアサーションのおかげでこの型を取得できています👍 -
[number]
なんやこれ?と思った人もいるでしょう。タプルに対して[number]
を使うと、全てのインデックスの要素の型をユニオン型で取得します。
つまりは、超わかりやすく書くと
language[0]
の型:"JavaScript"
language[1]
の型:"TypeScript"
language[2]
の型:"Python"
language[3]
の型:"Java"
language[4]
の型:"C#"
結果、
type TLanguage = (typeof language)[number];
// TLanguage = "JavaScript" | "TypeScript" | "Python" | "Java" | "C#"
となるわけです!
タプル型とインデックスアクセス型についてはこちら
extends君
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
const member = {
name: "ゆせ",
age: 21,
language: "TypeScript",
};
type TMember = keyof typeof member;
// TMember = "name" | "age" | "language"
今回やりたいことが、「変数のプロパティ名の取得」だとします。つまり、nameやageを取得したいということです。
おさらいになりますが、プロパティ名の取得はkeyof
でできるものの、型にのみ使用可能でしたね。変数には使用できません。memberは変数なので、直接keyof
のみで取得することはできません。
じゃあどうすればいいかと言うと、変数member
の型を取得し、それをkeyof
に当ててあげればいいわけです!イメージ↓↓
-
typeof member
でmemberの型を取得
name:string, age:number, ...
といった型を取得できます。 - 1で取得した型を
keyof
に当てる
そうすれば、name
,age
,language
を取得できますね!
1と2を合体させたコードが
type TMember = keyof typeof member;
になります!英語や普段のプログラミングと同じで、直感的にわからなければ分解していくと少しわかりやすいですね!次はもう少し複雑です
実際に使われるケースその2
const member = {
name: "太郎",
age: 21,
language: "TypeScript",
} as const;
type TMember = (typeof member)[keyof typeof member];
// TMember = "太郎" | 21 | "TypeScript"
いかがでしょう。今まで解説した通りにちゃんと考えることができればTMember
の実態がわかるかと思いますが、とは言っても直感的にはわかりにくいものなので解説していきます。先ほどと同様、分解して見ていきましょう!
- ここでの
typeof member
memberにはas const
がついていますね。読み取り専用readOnly
になります。先ほどは配列の中身をそのままで取得していましたが、今回はどうでしょう。以下がその結果です。
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
がなければ、上記の通りそれぞれの型をプリミティブに取得します。
2. [keyof typeof member]
を忘れずに
先ほど上でやったことと全く同じです。一言で言うと、変数の中のプロパティ名を取得します。つまりここで言う、name, age等の取得をしています。
3. 1と2でやったことを合体しよう!(typeof member)[keyof typeof member]
この形(typeof member)[keyof typeof member] ≒ (型[キー])
はインデックスアクセス型です。全てのインデックスの要素の型をユニオン型で取得します。結論から見た方が早いのでわかりやすく書きます。
member[name]
→ "太郎"
member[age]
→ "21"
member[language]
→ "TypeScript"
member
は読み取り専用そのものの型です。これは1でやったことです。
それにインデックス番号をつけてあげたく、今回はインデックス番号というの名の、プロパティ名を指定してあげるとその値を取得できるということですね。プロパティ名を取得し指定している部分([name]
とか)が2でやったことです。
インデックスアクセス型は特定の値を指定しない限りすべて取得するんでしたよね。今回はそれに該当するので、すべて取得し、結果
const member = {
name: "太郎",
age: 21,
language: "TypeScript",
} as const;
type TMember = (typeof member)[keyof typeof member];
// TMember = "太郎" | 21 | "TypeScript"
となります!
まとめ
かなり細かく解説しているところもあり、文章が長くなってしまいました申し訳ないです🙇♂️
とにかくこの記事広範囲でkeyof
,typeof
を使ったと思います!私が書いた解説を読みながら、「あれ?typeofってなんだっけ?」なんてこともあるかと思います。そんな時は初めに解説した部分に戻って都度確認しにいってください!
私もこの記事を書く前よりkeyofとtypeofの解像度が高まりました、考える、触れる反復がとにかく大事だと実感できました。
この記事が誰かのお役に立てれば嬉しいです!