やあ。今日も元気にコード書いてるかい?
「TypeScriptを導入したいけど、レガシーな巨大コードベースとビルド構成の壁があって、今すぐには移行できない……」
そんな現場の叫びを、シニアである君なら一度や二度は耳にしたことがあるはずだ。
「じゃあ、全ファイルに `// @ts-check` を入れて、JSDocで型を縛っていこうぜ」
――そう提案したときに、チームの後輩から「JSDocって、ただのコメントですよね? 何の意味があるんですか?」なんて冷めた目を向けられたことはないだろうか。
おいおい、舐めてもらっては困る。
現代のVSCodeやTypeScriptの言語サーバー(tsserver)にとって、JSDocは単なる「コメント」ではない。JavaScriptの世界にいながら静的型チェックの恩恵をフルに受けるための、最強のインターフェース定義書なんだ。
今日は、TypeScriptを入れずとも、いや、むしろ純粋なJavaScriptのまま堅牢なコードベースを築くための `@param` と `@returns` の極意を、ブラウザの裏側の動きや実務の泥臭い知見を交えて徹底的に叩き込んでやろう。
—
なぜ、いま「JSDoc」なのか?
JavaScriptは動的型付き言語だ。変数や関数の引数に「何を入れてもいい」という圧倒的な自由度がある。しかし、この自由度は、開発規模が数万行を超えた瞬間に「魔窟」へと姿を変える。
「この関数の第2引数、オブジェクトだっけ?それとも文字列だっけ?あ、前任者はここにプリミティブじゃなくてDOM要素を渡してるぞ……」なんて恐怖体験、君もしたことがあるはずだ。
ブラウザのJavaScriptエンジン(V8など)は、実行時にJSDocなど見ちゃいない。動的に型が変わる(隠しクラスの変更など、最適化の文脈では厄介な話もあるが、それはまた別の機会だ)コードをガリガリと実行していく。
しかし、エディタ(開発時の私たち)は別だ。
`// @ts-check` をファイルの先頭に仕込み、適切に `@param` と `@returns` を記述しておけば、VSCodeはJSDocを解析し、TypeScriptと同等の型推論とエラー検知をリアルタイムで行ってくれる。ビルドツールを変える必要もない。今日から、この瞬間から導入できる。これがJSDocの最大の武器だ。
—
基本構文と、現場でやりがちな「痛い」ミス
まずは基本の「キ」からおさらいしておこう。
関数インターフェースを定義する基本形はこうだ。
// @ts-check
/
- ユーザーの年齢を計算する
- @param {string} birthDate – YYYY-MM-DD形式の誕生日文字列
- @returns {number} 計算された年齢
/
function calculateAge(birthDate) {
const diff = Date.now() – new Date(birthDate).getTime();
const ageDate = new Date(diff);
return Math.abs(ageDate.getUTCFullYear() – 1970);
}
非常にシンプルだな。だが、実務の現場では、これだけでは足りないケースが多々ある。中級から一歩抜け出すために、よくある「落とし穴」を見ていこう。
落とし穴1: オプショナル引数とデフォルト値の表現
「この引数は渡してもいいし、渡さなくてもいい(オプショナル)」というケースはよくある。JSDocでこれを表現するには、ブラケット `[]` を使う。
/
- APIリクエストを実行する
- @param {string} url – リクエスト先URL
- @param {Object} [options] – fetchのオプション(省略可能)
- @param {string} [options.method=’GET’] – HTTPメソッド
- @returns {Promise
}
/
async function apiRequest(url, options = {}) {
const method = options.method || ‘GET’;
return fetch(url, { …options, method });
}
`[options]` と書くことで、これがオプショナルであることをTypeScriptのパーサーに伝えている。さらにオブジェクトのプロパティまで型縛りできるのだから強力だ。
落とし穴2: 複数の型を受け入れる(ユニオン型)
JavaScriptでは「文字列も数値を許容したい」という場面がよくある。JSDocでユニオン型を表現するには、パイプ `|` を使う。
/
- 要素のIDまたはDOM要素そのものを受け取って高さを取得する
- @param {string | HTMLElement} target – セレクタ文字列または要素
- @returns {number} 要素の高さ
/
function getElementHeight(target) {
const el = typeof target === ‘string’ ? document.querySelector(target) : target;
if (!el) return 0;
return el.getBoundingClientRect().height;
}
どうだ? `typeof` によるランタイムの型ガードと、JSDocの型定義が美しく噛み合っているのがわかるだろうか。
—
実務で即採用できる!リッチな型定義の実践コード
それでは、実務のフロントエンド開発でよく遭遇する、少し複雑なユースケースを想定したサンプルコードを見せよう。
APIから受け取ったデータを加工し、コールバック関数に渡すユーティリティモジュールをイメージしてほしい。
// @ts-check
/
- @typedef {Object} User
- @property {number} id – ユーザーID
- @property {string} name – ユーザー名
- @property {string} [email] – メールアドレス(オプショナル)
/
/
- @typedef {Object} FetchUsersOptions
- @property {number} [limit=10] – 取得件数
- @property {boolean} [includeEmail=false] – メールアドレスを含めるか
/
/
- ユーザーデータをフェッチし、条件に応じてフィルタリングする
- @param {string} endpoint – APIエンドポイント
- @param {FetchUsersOptions} [options={}] – フェッチオプション
- @returns {Promise
} ユーザー情報の配列を返すPromise
/
async function fetchAndProcessUsers(endpoint, options = {}) {
// デフォルト値のフォールバック
const limit = options.limit ?? 10;
const includeEmail = options.includeEmail ?? false;
const response = await fetch(`${endpoint}?limit=${limit}`);
if (!response.ok) {
throw new Error(‘データの取得に失敗しました’);
}
/ @type {User[]} /
const rawData = await response.json();
// メールアドレスが不要な場合はマスキングする処理
return rawData.map(user => ({
id: user.id,
name: user.name,
email: includeEmail ? user.email : undefined
}));
}
// — 使用例(VSCode上で型チェックが働く)—
// 誤った引数を渡すと、エディタが赤く波線を引いて怒ってくれる
fetchAndProcessUsers(‘/api/users’, { limit: 5, includeEmail: true })
.then(users => {
users.forEach(u => {
console.log(u.name, u.email);
});
})
.catch(err => {
console.error(err);
});
このコードのポイントは `@typedef` を使っている点だ。
オブジェクトの構造(インターフェース)に名前をつけ、複数の関数間で再利用できるようにしている。TypeScriptにおける `interface` や `type` 宣言とまったく同じことが、純粋な `.js` ファイルの中で実現できているのがおわかりいただけるだろうか。
—
チーフアーキテクトからの実践アドバイス
最後に、現場でJSDocを運用する上での心構えをいくつか伝授しておこう。
1. 「完璧」を目指さない
最初からすべてのファイルにJSDocを書こうとすると息切れする。まずはユーティリティ関数や、チーム内で共通利用するヘルパー関数など、「バグの温床になりやすい場所」から段階的に導入していくのがプロのやり方だ。
2. `// @ts-check` はファイルの先頭に
これを忘れると、どんなに美しいJSDocを書いてもエディタは黙殺する。忘れないようにスニペットに登録しておくといい。
3. ランタイムのバリデーションを過信しない
JSDocはあくまで「開発時の静的解析」のためのものだ。外部APIから飛んできたJSONデータが本当にその型を維持しているかは別問題なので、実務では `typeof` 判定や、Zodなどのランタイムバリデーションライブラリと組み合わせるのが、堅牢なフロントエンドを作るための極意となる。
JSDocによるインターフェース定義は、レガシーなJavaScriptプロジェクトを救うための強力な処方箋だ。「TypeScriptへ移行する予算も時間もない」と嘆く前に、まずは明日の朝、お気に入りのエディタでファイルの先頭に `// @ts-check` を書き、 `@param` を添えてみろ。
見慣れたコードベースが、ぐっと頼もしい相棒に見えてくるはずだ。

コメント