JavaScript から TypeScript への移行ガイド
JavaScript から TypeScript への移行は、コードベースの保守性と開発体験(DX)を劇的に向上させます。
本ガイドでは、そのプロセスを一歩ずつ順を追って解説していきます。
1. 準備フェーズ
1.1 コードベースの現状把握
移行を開始する前に、以下の点を確認してください:
- コードベースの規模と複雑さを特定する
- ビルドプロセスと依存関係をドキュメント化する
- 既存の型定義ファイル(
.d.tsファイル)があるか確認する - 特別な注意が必要なクリティカルパス(重要な処理)を特定する
1.2 バージョン管理の設定
クリーンな Git リポジトリ(または同等のもの)があることを確認してください。
CLI コマンド
# 移行用の新しいブランチを作成
git checkout -b typescript-migration
# 現在の状態をコミット
git add .
git commit -m "Pre-TypeScript migration state"2. コンフィギュレーション(設定)
2.1 TypeScript のインストール
TypeScript を開発依存関係(dev dependency)としてインストールします。
CLI コマンド
# TypeScript と Node.js 用の型定義をインストール
npm install --save-dev typescript @types/node2.2 tsconfig.json の作成
まずは基本的な tsconfig.json を作成します。
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
} 注意: サポートする最小限の環境に合わせて target を調整してください。
3. 移行のアプローチ
3.1 段階的な移行(Gradual Migration)
残りのファイルを JavaScript のまま保持しつつ、1ファイルずつ移行します。
tsconfig.json 設定例
{
"compilerOptions": {
"allowJs": true,
"checkJs": true
}
}- 最適: 大規模なコードベース、開発の中断を最小限に抑えたい場合。
3.2 一括移行(All-at-Once Migration)
すべての .js ファイルを .ts にリネームし、発生したエラーをすべて修正します。
CLI コマンド
# すべての JS ファイルを TS にリネーム(Linux/macOS)
find src -name "*.js" -exec sh -c 'mv "$0" "${0%.js}.ts"' {} \;- 最適: 小・中規模プロジェクト、または新規プロジェクトの初期段階。
重要な注意点: 大規模プロジェクトでは、混乱を最小限に抑え、プロセスを管理しやすくするために段階的な移行アプローチを強く推奨します。
4. ステップ・バイ・ステップの移行プロセス
4.1 基本設定の開始
以下の推奨設定で、基本的な tsconfig.json を作成します。
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src",
"allowJs": true,
"checkJs": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}4.2 JavaScript での型チェックの有効化
JavaScript ファイルの先頭に // @ts-check を追加することで、型チェックを有効にできます。
コード例
// @ts-check
/** @type {string} */
const name = 'John';
// TypeScript がこのエラーをキャッチします
name = 42; // エラー: 型 '42' を型 'string' に割り当てることはできません 注意: 特定の行の型チェックを無効にするには // @ts-ignore を使用します。
4.3 ファイル拡張子を .ts に変更
重要度の低いファイルから順に、拡張子を .js から .ts に変更していきます。
CLI コマンド
# 単一ファイルのリネーム
mv src/utils/helpers.js src/utils/helpers.ts
# またはディレクトリ内のすべてのファイルをリネーム(慎重に使用してください)
find src/utils -name "*.js" -exec sh -c 'mv "$0" "${0%.js}.ts"' {} \;4.4 型アノテーションの追加
コードに少しずつ型アノテーション(型定義)を加えていきます。
コード例
// 移行前
function add(a, b) {
return a + b;
}
// 移行後
function add(a: number, b: number): number {
return a + b;
}
// インターフェースの使用
interface User {
id: number;
name: string;
email?: string;
}
function getUser(id: number): User {
return { id, name: 'John Doe' };
}4.5 ビルドおよびテストスクリプトの更新
package.json を修正し、TypeScript のコンパイルを含めるようにします。
package.json
{
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"test": "jest"
}
}注意: テストフレームワークの設定も TypeScript ファイルを扱えるように更新してください。
5. 移行ツール
- ts-migrate: JavaScript から TypeScript への移行を自動化するツール(Facebook製)。
npx ts-migrate-full .- TypeStat: 型の安全性を保ちつつ JavaScript を TypeScript に変換します。
npx typestat- @types パッケージ: 依存関係にあるライブラリの型定義をインストールします。
npm install --save-dev @types/react @types/node
6. TypeScript 移行のベストプラクティス
6.1 小さく始めて繰り返す
- ユーティリティ関数や非 UI コンポーネントから始める
- 一度に移行するのは1ファイルまたは1モジュールに絞る
- 各移行ステップが成功するたびにコミットする
6.2 TypeScript 機能を活用する
コード例
// 可能な限り型推論(Type Inference)を活用する
const name = 'John'; // TypeScript は 'string' と推論します
const age = 30; // TypeScript は 'number' と推論します
// 柔軟性のためにユニオン型(Union Types)を使用する
type Status = 'active' | 'inactive' | 'pending';
// ランタイムチェックのために型ガード(Type Guards)を使用する
function isString(value: any): value is string {
return typeof value === 'string';
}6.3 サードパーティライブラリの処理
- 依存関係に対して
@typesパッケージをインストールする - 型定義がないライブラリには宣言ファイル(.d.ts)を作成する
- グローバルな型の拡張には
declare moduleを使用する
7. よくある課題と解決策
7.1 動的なプロパティ
課題: JavaScript ではオブジェクトをディクショナリとしてよく使用しますが、TS ではエラーになることがあります。
コード例
// 移行前
const user = {};
user.name = 'John'; // エラー: プロパティ 'name' は存在しません解決策: インデックスシグネチャ(Index Signature)または型アサーションを使用します。
コード例
// オプション 1: インデックスシグネチャ
interface User {
[key: string]: any;
}
const user: User = {};
user.name = 'John'; // OK
// オプション 2: 型アサーション
const user = {} as { name: string };
user.name = 'John'; // OK7.2 this コンテキストの扱い
課題: コールバック内での this のバインディングに関する問題。
コード例
class Counter {
count = 0;
increment() {
setTimeout(function() {
this.count++; // エラー: 'this' が定義されていません
}, 1000);
}
}解決策: アロー関数を使用するか、this を明示的にバインドします。
コード例
// 解決策 1: アロー関数
setTimeout(() => {
this.count++; // 'this' はレキシカルスコープから継承されます
}, 1000);
// 解決策 2: this をバインドする
setTimeout(function(this: Counter) {
this.count++;
}.bind(this), 1000);