おい、調子はどうだい?
最近、コードレビューをしていて「あぁ、もったいないな」って思うことがよくあるんだよね。APIから返ってきた定数オブジェクトや、設定ファイルのオブジェクトを手動で地道に型定義しているコードを見かけるたびに、「おいおい、それ `typeof` で一発だろ!」って心の中でツッコミを入れているんだ。
中級からもう一段階上のシニアへステップアップしたいなら、「DRY原則(Don’t Repeat Yourself)」をコードだけでなく型定義にも適用する視点が絶対に不可欠だ。今回は、値の世界と型の世界を鮮やかに繋ぐ架け橋、`typeof` 型演算子について、実務の現場でどう使い倒すべきか、俺のノウハウを余すところなく伝授しよう。
—
なぜ `typeof` 型演算子が実務で最強の武器になるのか?
TypeScriptを書き始めた頃って、どうしても「値」を書いたあとに、わざわざ同じ構造の「型(interface や type)」をもう一回書き直しがちだよね。例えば、こんなふうに。
// 二度手間でダサい書き方の例
const THEME_COLORS = {
primary: ‘#007bff’,
secondary: ‘#6c757d’,
success: ‘#28a745’,
} as const;
// わざわざ型を二重に定義している…メンテ漏れの温床だ!
type ThemeColorType = {
primary: string;
secondary: string;
success: string;
};
これ、もし将来 `danger` 色を追加することになったらどうなる? `THEME_COLORS` に追加して、さらに `ThemeColorType` にも追加して……って、絶対にどっちか忘れてバグる未来が見えるよね。
ここで登場するのが `typeof` 型演算子だ。これは、JavaScriptのランタイムにある `typeof` 演算子(文字列を返すやつ)とは全く別物で、TypeScriptのコンパイル時において「値から型を抽出する」ための強力なメタプログラミング的機能なんだ。
ブラウザやコンパイラ裏側の話
ちょっとエンジニアとして知っておいてほしいんだけど、TypeScriptの「型」は、ブラウザがコードを実行する時にはすべて綺麗に消し去られる(型消去:Type Erasure)。つまり、ランタイム(実行時)には `typeof` 型演算子は1バイトも残らない。
TypeScriptのコンパイラ(tsc)は、静的解析のフェーズでこの `typeof` を評価し、「この定数オブジェクトが持つ構造・リテラル型はこれだな」と推論して、一瞬で型定義に変換している。だから、実行時のパフォーマンスコストは完全にゼロ。安心してコードベース全体で使い倒していい。
—
現場で即コピペして使える!実践 `typeof` パターン集
百聞は一見にしかず。ここからは、俺たちが日々のフロントエンド開発(ReactやNext.jsなど)で直面するリアルなユースケースをベースに、具体的なコードを見ていこう。
1. 設定オブジェクトや定数マッピングからの型生成
一番よく使うのがこれだ。APIのエンドポイントや、画面のステータス管理、UIのバリエーション定義なんかで真価を発揮する。
/
- 注文ステータスの定義
- as const(constアセッション)を付けるのが極意!
- これにより、値がただの string ではなく、厳密なリテラル型になる。
/
export const ORDER_STATUS = {
PENDING: ‘PENDING’,
PROCESSING: ‘PROCESSING’,
SHIPPED: ‘SHIPPED’,
DELIVERED: ‘DELIVERED’,
CANCELLED: ‘CANCELLED’,
} as const;
// typeof と keyof を組み合わせて、キーのunion型を作る
// 結果: ‘PENDING’ | ‘PROCESSING’ | ‘SHIPPED’ | ‘DELIVERED’ | ‘CANCELLED’
export type OrderStatusKey = keyof typeof ORDER_STATUS;
// 値のunion型を作りたい場合はこうする
// 結果: ‘PENDING’ | ‘PROCESSING’ | ‘SHIPPED’ | ‘DELIVERED’ | ‘CANCELLED’
export type OrderStatusValue = typeof ORDER_STATUS[keyof typeof ORDER_STATUS];
// — 実務での使用例 —
function updateOrderStatus(status: OrderStatusValue) {
console.log(`ステータスを ${status} に更新します。`);
}
// ちゃんと型補完が効くし、タイポもコンパイルエラーで弾ける!
updateOrderStatus(ORDER_STATUS.SHIPPED);
> 💡 プロからのワンポイントアドバイス:
> `typeof` を使うときは、セットで `as const`(constアセッション) をつける癖をつけろ。これがないと、単なる `string` や `number` として推論されてしまい、`typeof` で取れる型が広すぎて使い物にならなくなる。「定数には `as const`、そして `typeof`」は、TypeScript界の黄金律だ。
2. APIのモックデータや初期値からの型推論
バックエンドのAPI仕様書がまだ完成していない段階や、フロントエンド側で先行してモックファーストで開発を進めるときにも `typeof` はめちゃくちゃ役に立つ。
// フロントエンド側で定義したモックデータ(または初期値)
const initialUserProfile = {
id: 1,
name: ‘山田 太郎’,
email: ‘taro.yamada@example.com’,
roles: [‘admin’, ‘user’] as const,
metadata: {
lastLogin: ‘2023-10-25T10:00:00Z’,
loginCount: 42,
},
};
// このオブジェクトの構造から、そのまま UserProfile 型を生成する
export type UserProfile = typeof initialUserProfile;
// Reactの useState などで初期値から型を自動推論させる場合にも直結する
// const [profile, setProfile] = useState(initialUserProfile);
わざわざ `interface UserProfile` を手書きして、バックエンドの変更に合わせてメンテし直す……なんて無駄な残業はもうやめよう。初期値やモックの構造を変えれば、型も勝手に追従してくれる。これが真のモダンフロントエンド開発だ。
3. 関数やコンポーネントの戻り値の型を抽出する (`ReturnType`)
少し応用編として、関数の戻り値から型を引っぺがすテクニックを紹介しよう。これには `ReturnType` というユーティリティ型と `typeof` を組み合わせる。
// 複雑なデータ加工を行うヘルパー関数
function createApiClient(baseURL: string) {
// なんやかんや複雑な初期化処理…
return {
endpoint: baseURL,
fetchData: async
const res = await fetch(`${baseURL}${path}`);
return res.json();
},
version: ‘v1.0.0’,
};
}
// 関数の戻り値の型を typeof と ReturnType で完全にキャプチャする
type ApiClient = ReturnType
// これにより、関数の実装が変わっても、それを利用する側の型定義を書き換える必要がなくなる
const client: ApiClient = createApiClient(‘https://api.example.com’);
このパターンの何が凄いかって言うと、「実装の正(Source of Truth)は常にコード(値)側にあり、型はその影に過ぎない」というTypeScriptの哲学を完璧に体現できる点なんだ。
—
シニアが教える「やりがちなアンチパターン」
ここまで `typeof` の素晴らしさを語ってきたけど、現場で誤った使い方をすると、逆にコードが読みにくくなってチームメンバーを混乱させる原因になる。注意すべきポイントもいくつか共有しておこう。
1. 何でもかんでも `typeof` すればいいわけではない
データベースのスキーマや、外部の厳格な公開APIのレスポンスなど、ドメインモデルとして明確に固定すべきものは、ちゃんと `interface` や Zod などのバリデーションライブラリを使って明示的に定義するべきだ。UIのコンポーネント内での状態や、ローカルな定数マッピングに対して `typeof` を適用するのが一番バリューを発揮する。
2. 循環参照や複雑怪奇な型のネスト
値が複雑に絡み合っているところで `typeof` を何階層も経由させると、エディタでマウスオーバーしたときに `type Foo = { a: { b: { c: … } } }` のような巨大な型が表示されてしまい、デバッグが地獄になる。型があまりに複雑化したら、一度立ち止まって型を分割しよう。
—
まとめ:型の重複地獄から抜け出そう
どうだったかい? `typeof` 型演算子は、単なる「便利な小ネタ」ではなく、TypeScriptの型システムとJavaScriptのランタイムをスマートに調停するための必須教養だ。
今日から君のプロジェクトにある「値と、そっくりそのまま手書きされた型定義」を見つけてみてほしい。きっと、「あ、ここ `typeof` で置き換えられるじゃん!」って気付く場所がたくさんあるはずだ。
コードベースから無駄な重複を削ぎ落とし、より堅牢で、変更に強いフロントエンドを一緒に作っていこうぜ。質問や「こんなケースはどう書くの?」ってのがあれば、いつでも気軽に声をかけてくれ。それじゃ、次のコードレビューを楽しみにしているよ!

コメント