やあ、開発は順調かな?
今日は、TypeScriptを触っていると必ずと言っていいほど直面する、しかし意外と「なんとなく」で使われがちな`keyof typeof`の組み合わせパターンについて深く掘り下げていこう。
現場でコードレビューをしていると、せっかくTypeScriptを使っているのに、オブジェクトのキーをわざわざ`type Role = ‘admin’ | ‘editor’ | ‘viewer’`なんて手書きしている例をよく見かける。それ、オブジェクト定義が変わった瞬間に型定義が壊れる「負債の種」だ。
今回は、実務で明日から使える「型と実体の同期術」を、アーキテクトの視点で徹底解説する。
—
なぜ `keyof typeof` が必要なのか?
モダンなフロントエンド開発において、僕たちが目指すべきは「Single Source of Truth(信頼できる唯一の情報源)」だ。
例えば、アプリ内のカラーパレットや、APIのエラーコード、権限設定など、「値(JavaScriptの実体)」として定義したオブジェクトがあるとする。このオブジェクトのキーを型として使い回したいとき、手動でユニオン型を作ると、実体に変更があった際に型を直し忘れるリスクが生まれる。
ここで登場するのが、`keyof` と `typeof` のコンボだ。
- `typeof`: JavaScriptの「変数」から、TypeScriptの「型」を抽出する。
- `keyof`: その「型」が持つキーを、ユニオン型(`’A’ | ‘B’`)として抽出する。
この2つを組み合わせることで、「JavaScriptのオブジェクトに追従する型」を動的に生成できるわけだ。
—
現場で即戦力になる3つのパターン
さあ、理屈はこれくらいにして、実際のコードを見ていこう。エディタに貼り付けて挙動を確認してみてほしい。
1. 基本形:設定オブジェクトからキーを抽出する
まずは最もオーソドックスなパターンだ。
// 1. 実体(JavaScriptのオブジェクト)を定義
const USER_ROLES = {
ADMIN: ‘System Administrator’,
EDITOR: ‘Content Editor’,
VIEWER: ‘Read Only User’,
} as const; // ここで ‘as const’ をつけるのがプロの鉄則(理由は後述)
// 2. 実体から型を逆引きする
// typeof USER_ROLES は { readonly ADMIN: string; … } という型を返す
// keyof はそのプロパティ名のユニオン型を作る
type UserRoleKey = keyof typeof USER_ROLES;
// 結果、UserRoleKey は ‘ADMIN’ | ‘EDITOR’ | ‘VIEWER’ と同等になる
// 3. 実務での利用例
function setPermission(role: UserRoleKey) {
console.log(`Setting permission for: ${USER_ROLES[role]}`);
}
// OK!
setPermission(‘ADMIN’);
// Error! 型安全が守られている(タイポも防げる)
// setPermission(‘GUEST’);
2. `as const`(Const Assertion)の重要性
ここで「なぜ `as const` をつけるのか?」という疑問を持ったなら、君はセンスがいい。
TypeScriptにおいて、`let` や通常の `const` で定義したオブジェクトのプロパティは、デフォルトでは「書き換え可能」と判断され、型が `string` に推論されてしまう。
const STATUS = {
OK: 200,
NG: 500
};
type StatusKey = keyof typeof STATUS; // これは ‘OK’ | ‘NG’ になるが…
type StatusValue = typeof STATUS[StatusKey]; // これは number 型になってしまう!
`as const` を付与することで、TypeScriptはそのオブジェクトを「読み取り専用のリテラル」として扱う。これにより、値そのものを型として抽出することが可能になるんだ。
3. 【応用】値(Value)のユニオン型を作る
実務では「キー」だけでなく、「値」のユニオン型が欲しい場面も多い。これも `keyof typeof` の応用でスマートに解決できる。
const THEME_COLORS = {
primary: ‘#007bff’,
secondary: ‘#6c757d’,
success: ‘#28a745’,
} as const;
// ステップ1: キーを抽出
type ColorKey = keyof typeof THEME_COLORS; // ‘primary’ | ‘secondary’ | ‘success’
// ステップ2: キーを使って値のユニオン型を抽出
type ColorValue = typeof THEME_COLORS[ColorKey]; // ‘#007bff’ | ‘#6c757d’ | ‘#28a745’
// これで、引数に具体的なカラーコードしか受け付けない関数などが作れる
const applyStyle = (color: ColorValue) => {
/ … /
};
—
ブラウザの裏側で何が起きているか
ここで少し、アーキテクトらしい話をしよう。
「`keyof typeof` を多用すると、ブラウザの実行速度に影響しませんか?」と聞かれることがある。
結論から言うと、影響はゼロだ。
TypeScriptの型定義(`type` や `interface`)、そしてこの `keyof typeof` による型推論は、ビルドプロセス(トランスパイル)の段階で完全に消滅する。ブラウザが実行するJavaScriptには、これらの型情報は一切残らない。
つまり、`keyof typeof` を使うことは、「実行時のオーバーヘッドを一切増やさずに、開発時の安全性だけを極限まで高める」という、非常にコストパフォーマンスの良い投資なんだ。
—
アンチパターン:`any` や `unknown` への逃げ
たまに、オブジェクトのキーを動的に扱う際に面倒になって `any` を使ってしまうケースを見かけるが、それは敗北宣言に等しい。
// 悪い例
function getInfo(key: string) {
return USER_ROLES[key as any]; // 型安全性が崩壊
}
// 良い例
function getInfo(key: UserRoleKey) {
return USER_ROLES[key]; // keyが適切であることをTSが保証してくれる
}
`keyof typeof` を使いこなせれば、キャスト(`as someType`)を最小限に抑えることができる。型キャストは「コンパイラを黙らせる行為」であり、本来は避けるべきものだ。
—
まとめ:型は「書く」ものではなく「導き出す」もの
中級以上のエンジニアへのステップアップとして、「型定義を二重管理しない」という意識を持ってほしい。
1. 実体(Object)を定義する
2. `as const` でリテラル化する
3. `keyof typeof` で型を抽出する
この流れを徹底するだけで、君のコードの堅牢性は劇的に向上する。もし将来、オブジェクトに新しいキーが追加されても、君が定義した型は自動的にそれを反映し、修正が必要な箇所をコンパイラが即座に教えてくれるようになる。
これが、TypeScriptが僕たちに提供してくれる本当の価値なんだ。
よし、今日の講義はここまで。さっそくプロジェクトの型定義を見直して、冗長なユニオン型を `keyof typeof` に置き換えてみてくれ。また何かあればいつでも聞いてほしい。ハッピーハッキング!

コメント