【実務・中級編】 keyof typeofの組み合わせパターン – TypeScript実践ガイド

やあ、開発は順調かな?
今日は、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` に置き換えてみてくれ。また何かあればいつでも聞いてほしい。ハッピーハッキング!

コメント

タイトルとURLをコピーしました