【実務・中級編】 @enumによる定数セットの型定義 – JavaScript実践ガイド

フロントエンドの現場で、こんなコードにイラッとした経験はないか?

// 「おいおい、ステータスコードの文字列なんて誰も覚えてねえよ!」事件
function updateOrderStatus(orderId, status) {
// 処理…
}

updateOrderStatus(1024, ‘completd’); // 痛恨のタイポ!静的解析も素通りして本番で爆発

TypeScript全盛の昨今なら `enum` や Union型(`’pending’ | ‘shipped’ | ‘completed’`)で一発レッドカードを出してくれるところだが、プロジェクトの都合や歴史的背景から「生粋のJavaScript(JSDoc)」で踏ん張らなきゃいけない現場だってまだまだ山ほどある。TypeScript導入のコストが高いからと諦めているそこの君、ちょっと待て。

実は、JSDocの `@enum` を使えば、JavaScriptのままでIDE(VS Codeなど)の強力な補完と型チェックの恩恵を100%受けることができる。今回は、TypeScriptに魂を売り渡さなくてもモダンな開発体験を手に入れる、シニアお墨付きのテクニックを伝授しよう。

—

なぜJavaScriptの「定数オブジェクト」は罠だらけなのか?

まず、JavaScriptのプリミティブとオブジェクトの挙動の基本に立ち返ろう。僕たちが普段やりがちな、ただのオブジェクトによる定数定義を見てほしい。

const ORDER_STATUS = {
PENDING: ‘pending’,
SHIPPED: ‘shipped’,
COMPLETED: ‘completed’
};

これの何が問題か? ブラウザのJavaScriptエンジン(V8など)の裏側を覗いてみれば一目瞭然だ。
この `ORDER_STATUS` はただの「書き換え可能なミュータブルなオブジェクト」にすぎない。うっかり別の場所で `ORDER_STATUS.PENDING = ‘hoge’` なんてやらかしても、JavaScriptは「おっ、いいよー」と涼しい顔で受け入れてしまう。

さらに、関数の引数に渡すとき、TypeScriptなら「`ORDER_STATUS` のいずれかの値」しか受け付けないように制限できるが、素のJSでは「任意の文字列(`string`)」がすべて許可されてしまう。ここにバグの温床がある。

これを解決するのが、JSDocの `@enum` タグだ。

—

`@enum` による定数セットの型定義:実践アプローチ

JSDocの `@enum` は、指定したオブジェクトを「列挙型(Enum)」としてIDEに認識させる魔法のコメントだ。これを使うと、単なるオブジェクトが「特定の型を持つ定数の集まり」へと生まれ変わる。

百聞は一見にしかず。現場でそのままコピペして使える、極上のサンプルコードを見せよう。

/

  • @file 注文ステータスの定義モジュール
  • @module orderStatus

/

/

  • 注文のライフサイクルを表す列挙型
  • @readonly
  • @enum {string}

/
export const OrderStatus = {
/ 決済待ち・未処理の状態 /
PENDING: ‘pending’,
/ 倉庫から出荷された状態 /
SHIPPED: ‘shipped’,
/ 顧客に配達が完了した状態 /
COMPLETED: ‘completed’,
/ キャンセルされた状態 /
CANCELLED: ‘cancelled’,
};

/

  • 注文ステータスに応じた処理を行う関数
  • @param {string} orderId – 対象の注文ID
  • @param {import(‘./orderStatus.js’).OrderStatus[keyof import(‘./orderStatus.js’).OrderStatus]} status – 許可されたステータス値のみを受け入れる
  • @returns {boolean} 処理が成功したかどうか

/
export function processOrder(orderId, status) {
// ステータスの妥当性チェック(実行時安全性の担保)
const validStatuses = Object.values(OrderStatus);
if (!validStatuses.includes(status)) {
console.error(`無効なステータスが渡されました: ${status}`);
return false;
}

console.log(`注文 ${orderId} のステータスを ${status} に更新します。`);
// ここに実際のビジネスロジックを書く…
return true;
}

このコードの何がスゴいのか?

1. IDEの圧倒的な補完力
VS Codeなどのエディタで `processOrder(‘123’, ` と打ち込んだ瞬間、IntelliSenseが `OrderStatus` の値(`pending`, `shipped`…)をドロップダウンでサジェストしてくれる。もうドキュメントをいちいち見に行く必要はない。
2. 静的解析(JSDoc / TypeScriptチェック)による検知
もしうっかり `processOrder(‘123’, ‘completd’)` とタイポして書いた場合、エディタ上に赤い波線(エラー)が出現する。コンパイルエラーではなくとも、エディタのリアルタイム検査でバグを事前に潰せるのは圧倒的なアドバンテージだ。
3. `Object.freeze()` との組み合わせで完全防御
実行時においても安全性を高めたいなら、次項のベストプラクティスを思い出してほしい。

—

シニアが教える、現場で絶対やるべきベストプラクティス

JSDocで型定義をするだけではなく、JavaScriptのランタイム側でも堅牢性を担保するのがプロの仕事だ。

1. `Object.freeze()` でイミュータブル(不変)にする

先ほども言った通り、通常のオブジェクトは書き換え可能だ。意図しない値の書き換えを防ぐために、定義したオブジェクトは必ず凍結(freeze)させよう。

export const OrderStatus = Object.freeze({
PENDING: ‘pending’,
SHIPPED: ‘shipped’,
COMPLETED: ‘completed’,
});

これで、万が一コードのどこかで `OrderStatus.PENDING = ‘foo’` と書いても、厳格モード(strict mode)下では即座にTypeErrorがスローされる。バグの早期発見において、これほど頼もしいことはない。

2. 型の再利用性を高める(JSDocのインポート)

別ファイルでこの定数型を使いたいときは、毎回長ったらしい型定義を書く必要はない。JSDocの `@typedef` や `import()` 構文を組み合わせることで、TypeScriptのUnion型と同等のものをJSファイル内でも再現できる。

/

  • @typedef {typeof OrderStatus[keyof typeof OrderStatus]} OrderStatusType

/

/

  • @param {string} orderId
  • @param {OrderStatusType} status – スッキリした型定義で再利用可能

/
function quickProcess(orderId, status) {
// 処理…
}

—

まとめ:JavaScriptの限界をJSDocでハックせよ

「フルTypeScriptでリプレイスしたい!」という叫びは、エンジニアなら誰もが心の中で一度は上げるものだ。しかし、予算や工数の関係で「素のJavaScript(ES Modules)でいかによいコードを書くか」という制約に縛られる現場も多い。

そんなとき、今回紹介した `@enum` による型定義と、`Object.freeze()` によるランタイムの安全性の組み合わせは、君のコードベースを劇的に救う銀の弾丸になり得る。

特別なビルドツールやコンパイラを導入しなくても、エディタのポテンシャルをJSDocで極限まで引き出す。この「ちょっとした一手間」を惜しまない姿勢こそが、優れたフロントエンドエンジニアと、そうでないエンジニアを分ける境界線だ。

さあ、今日の業務コードから古い定数オブジェクトを書き換えて、チームメンバーをうならせてやろうぜ。

コメント

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