メインコンテンツまでスキップ

keyof型演算子

keyofはオブジェクト型からプロパティ名を型として返す型演算子です。たとえば、nameプロパティを持つ型に対して、keyofを使うと文字列リテラル型の"name"が得られます。

ts
type Person = {
name: string;
};
type PersonKey = keyof Person;
type PersonKey = "name"
ts
type Person = {
name: string;
};
type PersonKey = keyof Person;
type PersonKey = "name"

2つ以上のプロパティがあるオブジェクト型にkeyofを使った場合は、すべてのプロパティ名がユニオン型で返されます。

ts
type Book = {
title: string;
price: number;
rating: number;
};
type BookKey = keyof Book;
// 上は次と同じ意味になる
type BookKey = "title" | "price" | "rating";
ts
type Book = {
title: string;
price: number;
rating: number;
};
type BookKey = keyof Book;
// 上は次と同じ意味になる
type BookKey = "title" | "price" | "rating";

インデックス型にkeyofを用いると、インデックスキーの型が返ります。

ts
type MapLike = { [K: string]: any };
type MapKeys = keyof MapLike;
type MapKeys = string | number
ts
type MapLike = { [K: string]: any };
type MapKeys = keyof MapLike;
type MapKeys = string | number

キーがstringのインデックス型は、stringではなくstring | numberが返ります。number型のキーアクセスのobj[0]obj["0"]と同じになるからです。

Mapped Typeskeyofを用いると、そのキーの型が返ります。

ts
type MapLike = { [K in "x" | "y" | "z"]: any };
type MapKeys = keyof MapLike;
type MapKeys = "x" | "y" | "z"
ts
type MapLike = { [K in "x" | "y" | "z"]: any };
type MapKeys = keyof MapLike;
type MapKeys = "x" | "y" | "z"

プロパティを持たないオブジェクト型にkeyofを使うとnever型が返ります。

ts
type What = keyof {};
type What = never
ts
type What = keyof {};
type What = never

any型にkeyofを使うとstring | number | symbol型が返ります。

ts
type AnyKeys = keyof any;
type AnyKeys = string | number | symbol
ts
type AnyKeys = keyof any;
type AnyKeys = string | number | symbol

keyofのメリット

keyofのメリットは、保守性が上がる点です。オブジェクト型とは別にプロパティ名のユニオン型を定義していると、オブジェクト型のプロパティを変更したときに、そのユニオン型のほうも修正が必要になります。keyofを使って、オブジェクト型からキーを導出するようにしておけば、変更箇所はオブジェクト型のところだけになります。

加えて、プロパティが何十個もあるようなオブジェクトを想像してみてください。そのプロパティ名のユニオン型を定義する必要が出てきたとします。その際に、プロパティ名をすべて転記するとなると、転記漏れや書き間違いもあるでしょう。そういう場合はkeyofを使うとそもそも書き写す必要がないため、便利な上に安全なコーディングができます。

keyofはMapped Typesと一緒に使われる

keyofは単体で使うことよりMapped Typesと組み合わせて使われることが多いです。

📄️ Mapped Types

インデックス型では設定時はどのようなキーも自由に設定できてしまい、アクセス時は毎回undefinedかどうかの型チェックが必要です。入力の形式が決まっているのであればMapped Typesの使用を検討できます。
  • 質問する ─ 読んでも分からなかったこと、TypeScriptで分からないこと、お気軽にGitHubまで🙂
  • 問題を報告する ─ 文章やサンプルコードなどの誤植はお知らせください。