TypeScript JSDoc
TypeScript を JSDoc と組み合わせて使用することで、JavaScript ファイルを .ts に変換することなく型チェックを追加できます。
これは、コードベースを段階的に移行する場合や、既存の JavaScript プロジェクトに型安全性(Type Safety)を導入したい場合に最適なアプローチです。
1. はじめに
JavaScript ファイルで TypeScript によるチェックを有効にするには、以下の手順が必要です。
tsconfig.jsonファイルを作成する(未作成の場合)checkJsを有効にするか、個別のファイルの先頭で// @ts-checkを使用する
1.1 型安全性のための JSDoc 例
コード例
// @ts-check
/**
* 2つの数値を加算します。
* @param {number} a
* @param {number} b
* @returns {number}
*/
function add(a, b) {
return a + b;
}2. オブジェクトとインターフェース
2.1 インラインでのオブジェクト型定義
コード例
// @ts-check
/**
* @param {{ firstName: string, lastName: string, age?: number }} person
*/
function greet(person) {
return `こんにちは、${person.firstName} ${person.lastName}`;
}
greet({ firstName: 'John', lastName: 'Doe' }); // OK
greet({ firstName: 'Jane' }); // エラー: プロパティ 'lastName' が不足しています2.2 複雑な型に対する @typedef の使用
コード例
// @ts-check
/**
* @typedef {Object} User
* @property {number} id - ユーザーID
* @property {string} username - ユーザー名
* @property {string} [email] - オプショナルなメールアドレス
* @property {('admin'|'user'|'guest')} role - ユーザーロール
* @property {() => string} getFullName - フルネームを返すメソッド
*/
/** @type {User} */
const currentUser = {
id: 1,
username: 'johndoe',
role: 'admin',
getFullName() {
return 'John Doe';
}
};
// TypeScript が User プロパティの補完(Autocomplete)を提供します
console.log(currentUser.role);2.3 型の拡張
コード例
// @ts-check
/** @typedef {{ x: number, y: number }} Point */
/**
* @typedef {Point & { z: number }} Point3D
*/
/** @type {Point3D} */
const point3d = { x: 1, y: 2, z: 3 };
// @ts-expect-error - z プロパティが不足しているためエラーを期待
const point2d = { x: 1, y: 2 };3. 関数型
3.1 関数宣言
コード例
// @ts-check
/**
* 長方形の面積を計算します
* @param {number} width - 長方形の幅
* @param {number} height - 長方形の高さ
* @returns {number} 計算された面積
*/
function calculateArea(width, height) {
return width * height;
}
// TypeScript は引数と戻り値の型を認識します
const area = calculateArea(10, 20);3.2 関数式とコールバック
コード例
// @ts-check
/**
* @callback StringProcessor
* @param {string} input
* @returns {string}
*/
/**
* @type {StringProcessor}
*/
const toUpperCase = (str) => str.toUpperCase();
/**
* @param {string[]} strings
* @param {StringProcessor} processor
* @returns {string[]}
*/
function processStrings(strings, processor) {
return strings.map(processor);
}
const result = processStrings(['hello', 'world'], toUpperCase);
// 結果は ['HELLO', 'WORLD'] となります3.3 関数のオーバーロード
コード例
// @ts-check
/**
* @overload
* @param {string} a
* @param {string} b
* @returns {string}
*/
/**
* @overload
* @param {number} a
* @param {number} b
* @returns {number}
*/
/**
* @param {string | number} a
* @param {string | number} b
* @returns {string | number}
*/
function add(a, b) {
if (typeof a === 'string' || typeof b === 'string') {
return String(a) + String(b);
}
return a + b;
}
const strResult = add('Hello, ', 'World!'); // string 型
const numResult = add(10, 20); // number 型4. 高度な型
4.1 ユニオン型とインターセクション型
コード例
// @ts-check
/** @typedef {{ name: string, age: number }} Person */
/** @typedef {Person & { employeeId: string }} Employee */
/** @typedef {Person | { guestId: string, visitDate: Date }} Visitor */
/** @type {Employee} */
const employee = {
name: 'Alice',
age: 30,
employeeId: 'E123'
};
/** @type {Visitor} */
const guest = {
guestId: 'G456',
visitDate: new Date()
};
/**
* @param {Visitor} visitor
* @returns {string}
*/
function getVisitorId(visitor) {
if ('guestId' in visitor) {
return visitor.guestId; // TypeScript はこれが Visitor (guestId持ち) であることを認識
}
return visitor.name; // TypeScript はこれが Person であることを認識
}4.2 マップ型(Mapped Types)と条件付き型(Conditional Types)
コード例
// @ts-check
/** * @template T * @typedef {[K in keyof T]: T[K] extends Function ? K : never}[keyof T] MethodNames */
/** * @template T * @typedef {{ * [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] * }} Getters */
/** @type {Getters<{ name: string, age: number }> } */
const userGetters = {
getName: () => 'John',
getAge: () => 30
};
// TypeScript は戻り値の型を強制します
const name = userGetters.getName(); // string 型
const age = userGetters.getAge(); // number 型5. 型のインポート
5.1 他のファイルからの型インポート
コード例
// @ts-check
// TypeScript ファイルから型をインポート
/** @typedef {import('./types').User} User */
// node_modules から型をインポート
/** @typedef {import('express').Request} ExpressRequest */
// リネームしてインポート
/** @typedef {import('./api').default as ApiClient} ApiClient */5.2 宣言ファイル(.d.ts)の作成
プロジェクトに types.d.ts ファイルを作成します。
types.d.ts
declare module 'my-module' {
export interface Config {
apiKey: string;
timeout?: number;
retries?: number;
}
export function initialize(config: Config): void;
export function fetchData<T = any>(url: string): Promise<T>;
}これを JavaScript ファイルで使用します。
コード例
// @ts-check
/** @type {import('my-module').Config} */
const config = {
apiKey: '12345',
timeout: 5000
};
// TypeScript は補完と型チェックを提供します
import { initialize } from 'my-module';
initialize(config);6. ベストプラクティス
TypeScript と JSDoc を併用する際は、以下のベストプラクティスに従ってください。
- 型チェックを行いたいファイルの先頭で必ず
// @ts-checkを有効にする - 複数の場所で使用される複雑な型には
@typedefを使用する - すべての関数の引数と戻り値の型をドキュメント化する
- ジェネリックな関数や型には
@templateを使用する - 型定義のないサードパーティライブラリには宣言ファイル(.d.ts)を作成する
- エラーが予想される箇所では
@ts-ignoreではなく@ts-expect-errorを使用する
7. よくある落とし穴
以下の一般的な問題に注意してください。
- // @ts-check の欠落: これがないと型チェックは動作しません。
- 不正確な JSDoc 構文: わずかなタイポ(打ち間違い)で型チェックが無効になることがあります。
- 型の競合: 異なるソースからの型が一致しない場合に発生します。
- 推論の問題: TypeScript が型を正しく推論できない場合があります。
- パフォーマンス: 複雑な型を持つ巨大な JavaScript ファイルは、チェックに時間がかかることがあります。
8. まとめ
TypeScript と JSDoc を組み合わせることで、ファイルを TypeScript に変換することなく、JavaScript プロジェクトに強力な型安全性を追加できます。
このアプローチは、特に以下のような場合に有用です。
- JavaScript コードベースから TypeScript への段階的な移行
- 既存の JavaScript プロジェクトへの型チェックの導入
.tsファイルがサポートされていない環境での作業- 型情報による JavaScript コードのドキュメント化
本チュートリアルで紹介したパターンとベストプラクティスに従うことで、JavaScript を使い続けながら TypeScript の多くのメリットを享受できます。
リマインダー: JSDoc は優れた型チェックを提供しますが、新規プロジェクトや完全な移行を検討している場合は、最高の開発体験を得るために .ts ファイルの使用を推奨します。