TypeScriptのkeyofが分からない人向け|typeof併用まで完全理解
TypeScriptのkeyofを「なんとなく」使っていて、思ったキーの型にならなかったり、keyof typeof の typeof を忘れてエラーで止まったりした経験はありませんか。
この記事では、keyofでオブジェクト型からキーの型を取り出す基本、値からキー型を作る keyof typeof の定番、ジェネリクスを使った型安全なプロパティアクセス、そして any・配列・Object.keys() でつまずく落とし穴までを、実際のコードと出力で確認します。読み終えれば、keyofが返す型を自分で予測でき、エラーが出ても原因を型のしくみから切り分けられるようになります。
この記事は次のような方におすすめです。
- keyofを使ってはいるが、何が入力で何が返るのか整理できていない方
- keyof typeof でキー型を作る定番の書き方を身につけたい方
- ジェネリクスと組み合わせた型安全なプロパティアクセスを書きたい方
- any・配列・Object.keys() で型が崩れる原因を自力で読めるようになりたい方
keyofとtypeofの役割と評価順がつかめると、設定オブジェクトのキーをタイポなく型で守れるようになり、型エラーのメッセージも「どこを直せばよいか」が見えるようになります。
それでは、順を追って詳しく見ていきましょう!
- 未経験で後悔したくない
【実体験】未経験からITエンジニアに転職して後悔した話|4社経験してわかった「最初の選択ミス」 - 年収が低くて不安
4年間ずっと年収260万だったエンジニアが、転職で510万になるまでの全記録
keyofとは|オブジェクト型からキーの型を取り出す演算子
keyofは「型」を入力に取り、そのプロパティキーを表すユニオン型を返す型レベルの演算子です。 値そのものではなく型に作用する点が出発点になります。
たとえばオブジェクト型を渡すと、キー名を文字列リテラルのユニオンとして取り出せます。
type K = keyof { name: string; age: number };
// type K = "name" | "age"
K は "name" | "age" というユニオン型になり、これは name か age 以外の文字列を受け付けません。keyofが返すキーの型は、対象のキーがどう宣言されているかで変わります。プロパティ名が文字列リテラルなら文字列リテラルのユニオンになりますが、インデックスシグネチャやsymbolキーを含む型では number や symbol を含むこともあります。
ここで押さえたいのは、keyofは値に対してではなく型に対して使うという点です。{ name: string; age: number } は型注釈であって実行時の値ではありません。実際の値(定数やオブジェクト変数)からキー型を作りたい場合は、いったん値を型に変換する一手間が必要になります。
keyofの基本的な使い方とよくある型エラー
keyofは interface でも type エイリアスでも同じように使えます。対象の型に宣言されたキーが、そのままユニオン型として取り出されるのが基本動作です。
interface Article {
title: string;
views: number;
published?: boolean; // optional でも抽出される
}
type ArticleKey = keyof Article;
// type ArticleKey = "title" | "views" | "published"
optional(?)が付いたプロパティもキーとしては抽出され、published はキーのユニオンに含まれます。readonly のような修飾も同様にキーの有無とは無関係で、抽出結果には影響しません。プロパティが増えれば、その分だけキーのユニオンも広がります。
一方で間違えやすいのが、値に対して直接 keyof を使ってしまうケースです。keyofの右側に来てよいのは型であって、値(変数や定数)ではありません。
const article = { title: "intro", views: 10 };
type WrongKey = keyof article; // 例:TS2749 'article' refers to a value, but is being used as a type here.(TypeScriptバージョンにより文言が異なる場合あり)
この場合、article は値なので型の位置では使えません。直し方は、値を型に変換してから keyof を適用することで、具体的には keyof typeof article と書きます。
typeof型演算子とkeyofの関係
型の位置に書く typeof は、値(変数・定数)からその型を取り出す型レベルの演算子です。これにより、実行時に存在する値の形を、そのまま型として再利用できます。
const user = { id: 1, name: "taro", role: "admin" };
type UserType = typeof user;
// type UserType = { id: number; name: string; role: string }
typeof user は、user という値が持つプロパティ構成を型として書き起こしたものになります。ここで注意したいのは、この typeof はJavaScriptの式で使う typeof(typeof x === "string" のように文字列を返すもの)とは役割が異なるという点です。同じキーワードですが、型注釈の位置にあるか式の位置にあるかで意味が分かれます。型の位置にある typeof は文字列を返すのではなく、値の型そのものを生み出します。
keyofと併用するのは、手元にあるのが「型」ではなく「値」のときに、値→型→キー型という橋渡しが必要になるからです。先ほどの「値に直接 keyof を使えない」問題は、typeof を一段かませることで解決します。その合わせ技が次の keyof typeof です。
keyof typeofの定番パターン|値オブジェクトからキー型を作る
keyof typeof obj は、「typeofで値を型に変換」→「keyofでその型からキーを抽出」という2段階で評価されます。 内側の typeof obj がまず値を型に変え、その結果に対して外側の keyof が働く、という順序です。
const config = {
host: "localhost",
port: 3000,
secure: false,
};
type ConfigKey = keyof typeof config;
// type ConfigKey = "host" | "port" | "secure"
評価の流れを分解すると次のようになります。
typeof configが値configから{ host: string; port: number; secure: boolean }という型を作る- その型に
keyofが働き、"host" | "port" | "secure"というキーのユニオンになる
定番の使い所は、設定オブジェクトや定数群のキーを文字列で直書きせず、型として列挙したい場面です。キーを "host" のように直書きすると、タイポしてもコンパイル時に気づけませんが、keyof typeof config で型化しておけば、存在しないキーは型エラーで弾かれます。
さらに値そのものをリテラル型に絞りたい場合は、as const を併用すると効果的です。as const を付けると各プロパティの値が number のような広い型ではなくリテラル型に固定されるため、keyof typeof で取り出すキーと合わせて、キー・値の両方を型で守れるようになります。
const status = { draft: 0, published: 1, archived: 2 } as const;
type StatusKey = keyof typeof status;
// type StatusKey = "draft" | "published" | "archived"(キー側)
type DraftValue = (typeof status)["draft"];
// type DraftValue = 0(値側も 0 に固定。as const がないと number になる)
逆に、typeof を書き忘れて keyof config としてしまうと、値を型として扱おうとしてエラーになります。
const config2 = { host: "localhost", port: 3000 };
type Bad = keyof config2; // 例:TS2749 'config2' refers to a value, but is being used as a type here.(TypeScriptバージョンにより文言が異なる場合あり)
keyofで型安全なプロパティアクセスを作る
keyofをジェネリクスと組み合わせると、任意のオブジェクトから「存在するキーだけ」を受け取り、その値の型を正しく返す関数を書けます。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: "taro" };
const id = getProperty(user, "id"); // id は number
const name = getProperty(user, "name"); // name は string
ポイントは型引数の制約 K extends keyof T です。これによって key には T に実在するキーしか渡せなくなり、戻り型 T[K] が「そのキーに対応する値の型」へ正しく絞り込まれます。getProperty(user, "id") の戻りは number、getProperty(user, "name") の戻りは string というように、キーごとに型が変わります。
存在しないキーを渡すと、型レベルで弾かれます。
getProperty(user, "age"); // 例:TS2345 Argument of type '"age"' is not assignable to parameter of type '"id" | "name"'.(TypeScriptバージョンにより文言が異なる場合あり)
戻り型を決めているのは T[K] という書き方で、これはオブジェクト型から特定キーの値の型を取り出す記法です。K がユニオンのときは、対応する値の型のユニオンが返ります。K extends keyof T の extends は型引数に制約をかけるための書き方で、ここでは「K は T のキーのいずれかである」という条件を表しています。
<T> の基本がまだあいまいな方は、こちらのジェネリクスの基礎から押さえると、この関数の読み解きがぐっと楽になります。
keyofの戻り型が変わるケースと注意点
keyofが返す型は対象によって変わり、オブジェクト型以外を渡すと、文字列リテラルのきれいなユニオンにならないことがあります。代表的なケースを次の表にまとめます。
| 対象の型 | keyofの戻り型(条件付き) | ||
|---|---|---|---|
{ name: string; age: number } |
`”name” \ | “age”`(キーのリテラルのユニオン) | |
{ [k: string]: T }(文字列インデックスシグネチャ) |
`string \ | number`(数値キーも文字列として許容されるため) | |
| 配列・タプル | number や配列メソッド名などを含む型(length・push 等のキーも入る) |
||
any |
`string \ | number \ | symbol` |
{}(空オブジェクト) |
never に近い挙動(取り出せるキーがない) |
実際のコードで確認すると次のようになります。
type A = keyof any; // string | number | symbol
type B = keyof { [k: string]: number }; // string | number
type C = keyof number[]; // number | "length" | "push" | ... (配列が持つキーを含む)


ここで注意したいのは、断定しすぎないことです。たとえば配列に keyof を使うと、数値インデックスを表す number だけでなく length や各種メソッド名まで含まれます。インデックスシグネチャの戻りに number が混じるのは、JavaScriptではオブジェクトのキーが内部的に文字列化され、数値キーも文字列キーとして扱える条件があるためです。空オブジェクト {} のように取り出せるキーがない場合は、結果として何も割り当てられないキー型になります。対象が素直なオブジェクト型かどうかで戻り型が大きく変わるため、期待した型にならないときはまず対象の型を疑うとよいですよ。
keyofとObject.keys()の違い(型レベルと値レベル)
keyofと Object.keys() は似て見えますが、keyofは型レベルで厳密なキー型を返し、Object.keys() は値レベルで実行時のキー配列を返すという根本的な違いがあります。
| 観点 | keyof | Object.keys() |
|---|---|---|
| 動作する層 | 型レベル(コンパイル時) | 値レベル(実行時) |
| 結果 | キーのリテラルのユニオン型 | string[] |
| 型の厳密さ | 厳密(存在するキーだけ) | 緩い(ただの文字列配列) |
注意点は、Object.keys() の戻り型が string[] に固定されることです。実際のオブジェクトのキーがどれだけ厳密でも、型としては「文字列の配列」として扱われます。
const obj = { a: 1, b: 2 };
Object.keys(obj).forEach((key) => {
// key の型は string なので、obj[key] は型エラーになりやすい
const v = obj[key]; // 例:TS7053 インデックスシグネチャ不在(バージョンにより文言が異なる場合あり)
});
// 厳密に回したいときはキー配列の型を絞る
(Object.keys(obj) as Array<keyof typeof obj>).forEach((key) => {
const v = obj[key]; // v は number に絞られる
});
ループ内で obj[key] の型を効かせたいときは、Object.keys() の戻りを as Array<keyof typeof obj> で絞ると、キーが "a" | "b" として扱われ、値の型も正しく推論されます。ただしこのキャストは「実行時のキーが本当にその型に収まっている」前提に立つものなので、想定外のキーが混ざらない場面で使うのが安全です。
Mapped Typesでのkeyofの活用
keyofはMapped Typesの中核で使われ、{ [K in keyof T]: ... } という書き方で、ある型の全プロパティをまとめて変換できます。 K in keyof T は「T のキーを1つずつ取り出して K に当てはめる」という意味です。
type ReadonlyAll<T> = {
readonly [K in keyof T]: T[K];
};
interface Point { x: number; y: number }
type ROPoint = ReadonlyAll<Point>;
// type ROPoint = { readonly x: number; readonly y: number }
この例では、Point の全プロパティに readonly を付け直した型を生成しています。[K in keyof T] でキーを走査し、T[K] で各キーの値の型を取り出して、新しい型を組み立てているわけです。標準で用意されている多くのユーティリティ型も、このkeyofとMapped Typesの仕組みの上に成り立っています。
キーと値の型をまとめて定義したいときは Record 型が便利で、これもキーの型を起点に値の型を割り当てる発想に通じます。
実務でのkeyof活用例と「効かない」ときの判断フロー
keyofが期待どおりにキーを絞れないときは、「対象が型か値か」「typeofを挟むべきか」「any・配列・インデックスシグネチャの例外に当たっていないか」を順に確認すると切り分けられます。次のフローで上から見ていきます。
1. 対象は「型」か「値」か → 値(変数・定数)なら typeof を挟んで keyof typeof にする
2. それでも緩いなら、対象が any・配列・インデックスシグネチャでないか確認する → これらは戻り型が広がる
3. キー配列の型が string[] になっている → Object.keys() 由来の可能性が高い。必要なら as Array<keyof typeof obj> で絞る
実務では、設定オブジェクトやAPIレスポンスのキーを型化する場面でこの判断が役立ちます。たとえば設定値のキーを keyof typeof config で型化しておけば、設定項目を増減したときにキーの参照漏れやタイポを型で検出できます。APIレスポンスを扱うときも、レスポンスの形を型として定義し、そのキーを keyof で取り出しておくと、フィールド名の打ち間違いを未然に防げます。
よくある質問
keyof typeofの「typeof」を省略するとどうなりますか?
keyof config のように値へ直接 keyof を使うと、値を型として扱おうとしてエラーになります(例:TS2749 など。文言はバージョンで異なる場合があります)。型の位置の typeof が値を型に変換する役割を担うため、値からキー型を作るときは keyof typeof config と書くのが正解です。
keyofとObject.keys()はどちらを使うべきですか?
型を厳密に扱いたいなら keyof、実行時にキーの配列が欲しいなら Object.keys() を使います。Object.keys() の戻りは中身が厳密でも string[] になるため、ループ内で値の型を効かせたいときは as Array<keyof typeof obj> で絞ると安全です。目的が型か実行時かで使い分けましょう。
keyofで取れるキー型にnumberやsymbolが混ざるのはなぜですか?
JavaScriptのプロパティ名は基本的に文字列またはSymbolで、数値キーは文字列に変換されます。TypeScriptではその性質を反映して、文字列インデックスシグネチャなどで number が keyof に含まれることがあります。素直なオブジェクト型なら文字列リテラルのユニオンになりますが、インデックスシグネチャや配列、any を対象にすると number や symbol を含むキー型になることがあります。対象の型しだいで戻りが変わると覚えておくとよいですよ。
【付録】さらに学びを深めるためのリソース
さらにTypescriptの学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。
おすすめの書籍
ゼロからわかる TypeScript入門
技術評論社から出版されている「ゼロからわかる TypeScript入門」は、プログラミング初心者や本職プログラマーではない方を主な対象にした入門書です。
変数・条件分岐・ループといった基本から、クラスやインターフェース、モジュールまで段階的に学べる構成になっています。最終章ではWeb APIとJSONを使った非同期Webアプリの作成も体験できるので、「実際に動くものを作る」ところまで到達できます。
プロを目指す人のためのTypeScript入門
技術評論社の「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで」、通称 ブルーベリー本 です。
JavaScriptの仕様とTypeScript独自の機能を両方押さえつつ、リテラル型・ユニオン型・keyof型・ジェネリクスなど、高度な型表現まで踏み込んで解説しています。TypeScriptの型システムの表現力を本格的に学べる一冊です。
オンラインで参照できる公式ドキュメント
TypeScript公式ハンドブック
https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。
TypeScript Deep Dive
https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。
TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。
まとめ – keyofとtypeof併用で型からキーを取り出す要点
この記事の内容をまとめます。
- keyofは型を入力に取り、キーのユニオン型を返す型レベルの演算子で、値ではなく型に作用する
- 値からキー型を作るときは
keyof typeof objを使い、typeofで値→型、keyofでキー抽出という2段で評価される(as const併用で値もリテラル型に固定できる) K extends keyof Tをジェネリクスに使うと、存在するキーだけを受け取り戻り型を絞る型安全なプロパティアクセスが書けるany・配列・インデックスシグネチャでは戻り型が広がり、Object.keys()の戻りはstring[]になるなどの落とし穴がある
keyofとtypeofの役割と評価順をつかめば、思いどおりの型にならないときも「対象は型か値か」から落ち着いて切り分けられるようになります。
keyof typeof と相性のよい as const で定数をリテラル型に固定する書き方は、こちらで詳しく解説しています。
※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。






