はじめに
JavaScript から TypeScript への移行は、コードの型安全性を向上させ、バグの早期発見を可能にする重要な取り組みです。本記事では、以下の取り組みについて詳しく解説します。
- JavaScript から TypeScript への移行計画策定
- コードベースの分析と移行対象ファイルの特定
- TypeScript 設定ファイル(tsconfig.json)の最適化
- 型の設計と逐次的な移行手順の作成
- any 型や型安全性の改善
- コードのリファクタリングと必要な修正
- TypeScript を活用したバグの早期発見と修正
- IDEやCIツールの TypeScript サポート設定
- 開発チーム向けの TypeScript トレーニング
- 移行後のテストの強化と改善
移行を円滑に進めるための具体的な手順と、Before / After のサンプルコードを交えて解説します。
移行の目的を明確化
まず、TypeScript への移行の目的を明確にしましょう。一般的な目的として、以下のようなものが挙げられます。
- 型安全性の向上:ランタイムエラーを減らし、コードの信頼性を高める。
- 開発効率の向上:IDE の補完機能が強化され、バグの早期発見が可能になる。
- メンテナンス性の向上:チーム開発において、型情報を活用して意図を明確に伝えやすくする。
- ライブラリやフレームワークの互換性向上:多くの最新のライブラリが TypeScript をサポートしている。
移行対象ファイルの特定
全てのファイルを一度に TypeScript に変換するのは現実的ではありません。そのため、まずどのファイルを優先的に移行するかを決めることが重要です。
-
優先度高
- ユーティリティ関数(共通で使用されるため、型の恩恵を受けやすい)
- API クライアント(リクエスト/レスポンスの型を定義することで安全性が向上)
- 型の影響範囲が小さく、独立性の高いモジュール
-
優先度中
- 主要なコンポーネント(コンポーネントの props の型を定義することで可読性が向上)
- ビジネスロジックを含むサービス層
-
優先度低
- テストコード(移行後に型の恩恵を得られるが、最優先ではない)
- 一時的なスクリプトや古いコード
段階的な移行戦略の策定
移行は一気に行うのではなく、段階的に進めるのがベストです。以下のような戦略が考えられます。
- tsconfig.json を作成し、部分的な型チェックを導入
.js
ファイルを.ts
に変更し、エラーを確認- ユーティリティ関数やモデルの型定義を優先的に追加
- コンポーネントやビジネスロジック層を順次移行
- any を減らし、型の厳密さを高める
- 最終的にすべての
.js
ファイルを.ts
に変換し、TypeScript の strict モードを適用
必要なライブラリやツールの確認
TypeScript への移行には、いくつかのライブラリやツールが必要です。事前にインストールしておきましょう。
- TypeScript 本体
npm install --save-dev typescript
- 型定義パッケージ(@types)
@types/react
,@types/node
など、プロジェクトに応じた型定義を追加
npm install --save-dev @types/react @types/node
- ESLint + TypeScript の設定
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
eslintrc.json
の設定
{ "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint"], "rules": { "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/explicit-module-boundary-types": "off" } }
- Jest(テスト環境)と ts-jest の設定
npm install --save-dev jest ts-jest @types/jest
これらを整えておくことで、スムーズに TypeScript への移行を進めることができます。
TypeScript環境でJestをセットアップしたブログ記事もありますので合わせてご参考ください。
TypeScript 設定ファイル(tsconfig.json)の最適化
TypeScript の設定ファイル tsconfig.json
は、プロジェクトのコンパイル設定を制御する重要なファイルです。
適切に設定することで、型安全性を向上させ、開発時のエラーを未然に防ぐことができます。
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
compilerOptions(コンパイラオプション)
コンパイラの動作を制御するオプションを設定します。
strict: true
- TypeScript の厳格な型チェックを有効にします。
- 以下の strict* 系オプションをすべて true に設定するのと同等です。
- 推奨: true(型安全性を最大限に保つ)
noImplicitAny: true
- 暗黙的な any 型を禁止します。
- 型の明示が必要になり、型安全性が向上します。
- 推奨: true
strictNullChecks: true
- null や undefined を適切に型として扱うようにします。
- 例: string | null 型の変数に null を代入できるが、string 型の変数には null を代入できない。
- 推奨: true(null の取り扱いミスを防ぐ)
strictFunctionTypes: true
- 関数の引数や戻り値の型チェックを厳格にします。
- 型の互換性のルールが強化され、意図しない関数の使用ミスを防ぐことができます。
- 推奨: true
moduleResolution: "node"
- Node.js スタイルのモジュール解決を行います(node_modules からのパス解決を可能にする)。
- import 文を使って外部パッケージを適切にインポートできます。
- 推奨: "node"(一般的な TypeScript プロジェクトではデフォルト)
esModuleInterop: true
- default export を含む ES モジュールとの互換性を提供します。
- CommonJS (require) を使うライブラリと import の互換性を確保します。
- 推奨: true(ES モジュールと互換性を確保するため)
include(対象ディレクトリ)
コンパイル対象の TypeScript ファイルを含むディレクトリを指定します。
"src" を指定することで、src/ 内の .ts や .tsx ファイルのみを対象にできます。
"include": ["src"]
exclude(除外ディレクトリ)
TypeScript のコンパイル対象から除外するディレクトリを指定します。
"node_modules" や dist/ など、コンパイル不要なファイルを指定するのが一般的です。
"exclude": ["node_modules", "dist"]
その他、推奨オプション
プロジェクトによっては、下記のオプションをお勧めします。
forceConsistentCasingInFileNames: true
- 大文字・小文字の違いによるファイル名の衝突を防ぎます(特に Windows 環境で有効)。
- 推奨: true
noUnusedLocals: true
- 使用されていないローカル変数があるとエラーにします。
- 推奨: true(不要な変数を削除し、コードをクリーンに保つ)
noUnusedParameters: true
- 使用されていない関数の引数があるとエラーにします。
- 推奨: true(不要な引数の削除を促し、コードの明瞭性を向上)
noFallthroughCasesInSwitch: true
- switch 文の case で break を忘れた場合にエラーを出します。
- 推奨: true(バグを防ぐ)
型の設計と逐次的な移行手順
型の設計は移行の成功を左右します。以下の手順で進めましょう。
.js
ファイルを.ts
に変更any
を避け、基本的な型を定義- 型推論を活用しつつ、適切な型を適用
- 関数の引数と戻り値に型を追加
Before:
function sum(a, b) {
return a + b;
}
After:
function sum(a: number, b: number) {
return a + b;
}
any
型や型安全性の改善
最初は any
を使いたくなることもありますが、可能な限り避けて、適切な型を適用していきます。
Before:
function getUserData(): any {
return fetch('/api/user').then(res => res.json());
}
After:
type User = {
id: number;
name: string;
email: string;
};
async function getUserData(): Promise<User> {
const response = await fetch('/api/user');
return response.json();
}
コードのリファクタリングと必要な修正
コードを整理し、可読性やメンテナンス性を向上させるために、以下のリファクタリングを実施していきます。
require
を import
に置き換え
対象: CommonJS (require) を使用しているコード
対応: ES Modules (import) に書き換える
Before (CommonJS)
const util = require("./util");
After (ES Modules)
import util from "./util";
理由
- import は静的解析が可能で、ツリーシェイキングの恩恵を受けやすい
- TypeScript との相性が良く、型推論がしやすい
- ES Modules は将来的に標準となるため、統一することで可読性・保守性が向上する
クラスベースのコードを関数型コンポーネントに変換
対象: class を使った React コンポーネント
対応: React Hooks を使った関数型コンポーネントにリファクタリング
Before (クラスベースコンポーネント)
import React, { Component } from "react";
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increase</button>
</div>
);
}
}
export default Counter;
After (関数型コンポーネント)
import { useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
);
};
export default Counter;
理由
- 関数型コンポーネントはクラスベースよりもシンプルで、可読性が高い
- useState などの React Hooks を活用でき、ロジックの再利用が容易になる
- クラスの this を意識する必要がなくなり、バグを減らせる
interface
や type
で整理
型定義を 対象: PropTypes を使用しているコード、または型のないコード
対応: TypeScript の interface や type を活用
Before (PropTypes 使用)
import PropTypes from "prop-types";
const UserCard = ({ name, age }) => (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
</div>
);
UserCard.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
};
export default UserCard;
After (TypeScript interface 使用)
interface UserCardProps {
name: string;
age?: number;
}
const UserCard: React.FC<UserCardProps> = ({ name, age }) => (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
</div>
);
export default UserCard;
理由
- PropTypes は実行時の型チェックだが、TypeScript ならコンパイル時にエラーを検出できる
- interface や type を使うことで、型情報がIDEで補完され、開発効率が向上する
- 型の定義を統一することで、コードの可読性・保守性が向上する
TypeScript を活用したバグの早期発見と修正
TypeScript を導入すると、コンパイル時にバグを発見できます。例えば、以下のようなエラーを防げます。
Before:
const user = getUserData();
console.log(user.name.toUpperCase()); // user が null ならエラー
After:
const user: User | null = await getUserData();
console.log(user?.name.toUpperCase());
IDE や CI ツールの TypeScript サポート設定
TypeScript を導入すると、開発体験やコードの品質向上のために IDE と CI の設定を最適化することが重要です。
ここでは、VS Code の ESLint 設定と、GitHub Actions での型チェックについて詳しく説明します。
eslint
+ typescript-eslint
設定
VS Code の VS Code で TypeScript コードを静的解析し、型チェックやコーディングスタイルを統一するために、ESLint と @typescript-eslint を設定します。
手順
- ESLint と TypeScript ESLint パッケージをインストール
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
- ESLint 設定ファイル .eslintrc.js を作成
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
rules: {
'@typescript-eslint/no-unused-vars': ['error'],
'@typescript-eslint/explicit-function-return-type': 'off',
},
};
-
VS Code に ESLint 拡張機能をインストール
-
VS Code の settings.json に ESLint を有効化
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": ["typescript", "typescriptreact"]
}
動作確認
以下のコマンドで ESLint が正しく動作するか確認。
npx eslint src/**/*.ts --fix
tsc --noEmit
を実行し型チェック
GitHub Actions で 手順
- GitHub Actions のワークフロー定義を作成 .github/workflows/ci.yml を作成し、tsc --noEmit で型チェックを行う。
name: TypeScript Check
on:
pull_request:
push:
branches:
- main
jobs:
type-check:
runs-on: ubuntu-latest
steps:
- name: リポジトリをチェックアウト
uses: actions/checkout@v4
- name: Node.js をセットアップ
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- name: 依存関係をインストール
run: npm ci
- name: TypeScript の型チェックを実行
run: npx tsc --noEmit
GitHub に push すると、GitHub Actions 上で tsc --noEmit が実行され、型エラーがあれば検出される。
開発チーム向けの TypeScript トレーニング
チームメンバーが TypeScript をスムーズに使えるように、以下の取り組みを推奨します。
TypeScript の基本概念を学ぶ(型推論、ユニオン型、ジェネリクスなど)
まずは TypeScript の主要な概念を理解し、実際に手を動かして学ぶことが重要です。
型推論(Type Inference)
- TypeScript は明示的な型指定をしなくても、ある程度の型を自動推論する
let message = "Hello, TypeScript"; // string 型として推論
let count = 10; // number 型として推論
ユニオン型(Union Types)
- 変数に複数の型を許容する
function printId(id: string | number) {
console.log(`Your ID is: ${id}`);
}
printId(123); // OK
printId("abc"); // OK
ジェネリクス(Generics)
- 汎用的な型を定義し、型の柔軟性を確保
function identity<T>(arg: T): T {
return arg;
}
let result = identity<number>(10); // 型: number
let text = identity<string>("Hello"); // 型: string
型エイリアス(Type Aliases)とインターフェース(Interfaces)
- 型の再利用性を向上させる
type User = {
id: number;
name: string;
};
interface Product {
id: number;
name: string;
price: number;
}
const user: User = { id: 1, name: "Alice" };
const product: Product = { id: 1, name: "Laptop", price: 1000 };
チーム内で TypeScript のベストプラクティスを共有
実践的な場面での統一したコーディングスタイルやベストプラクティスを共有し、チームの生産性を向上させます。
共有すべきベストプラクティス
-
型推論を活用しつつ、明示的に型を指定すべき箇所を見極める
- 型推論を活かしつつ、曖昧な型(any)を避ける
-
不必要な any の使用を避ける
- noImplicitAny を有効にすることで未定義の any を防ぐ
- unknown や never を活用し、厳格な型チェックを実施
-
関数の引数と戻り値に明示的な型を指
function add(a: number, b: number): number {
return a + b;
}
- 型の安全性を意識したエラーハンドリング
function fetchData(): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data received");
}, 1000);
});
}
async function main() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error("Error:", error);
}
}
レビューで型の改善を促進
コードレビューの際に型の適用をチェックし、より良い書き方をチーム内で共有します。
型に関するレビューのポイント
- ✅ 適切な型が指定されているか?(不要な any の使用を避ける)
- ✅ 冗長な型指定がないか?(型推論を適切に活用できているか)
- ✅ null や undefined の考慮がされているか?
- ✅ ユニオン型・ジェネリクスの適用が適切か?
例: 型を改善するコードレビュー
// ❌ 改善前: any を使用
function getUser(id: any): any {
return { id, name: "John Doe" };
}
// ✅ 改善後: 明確な型を指定
function getUser(id: number): { id: number; name: string } {
return { id, name: "John Doe" };
}
実践的な学習方法
-
ハンズオン(Hands-on)ワークショップ
- 実際に TypeScript のコードを書きながら学習
- 例: 「型エイリアス vs インターフェースの使い分け」
-
ペアプログラミング
- 経験者とペアを組んでコーディングしながら学ぶ
-
定期的な勉強会
- 週1回の TypeScript ベストプラクティス共有会を開催
- 例: 「ユニオン型とインターセクション型の活用」
-
実際のプロジェクトで適用
- 小さなリファクタリングから始めて、段階的に TypeScript を導入
移行後のテストの強化と改善
TypeScript の導入により、テストの信頼性も向上します。
- Jest や Testing Library で型付きのテストを書く
ts-jest
を導入し TypeScript ベースのテストを実施@types/jest
をインストールし型チェックを強化
Before:
test('should return user name', () => {
expect(getUserData().name).toBe('Alice');
});
After:
test('should return user name', async () => {
const user = await getUserData();
expect(user.name).toBe('Alice');
});
まとめ
本記事では、JavaScript から TypeScript への移行について、以下の観点から詳しく解説しました。
- JavaScript から TypeScript への移行計画策定
- コードベースの分析と移行対象ファイルの特定
- TypeScript 設定ファイル(tsconfig.json)の最適化
- 型の設計と逐次的な移行手順の作成
- any 型や型安全性の改善
- コードのリファクタリングと必要な修正
- TypeScript を活用したバグの早期発見と修正
- IDEやCIツールの TypeScript サポート設定
- 開発チーム向けの TypeScript トレーニング
- 移行後のテストの強化と改善
JavaScript から TypeScript への移行は一朝一夕ではありませんが、計画的に進めることで確実に型安全性を向上させることができます。本記事で紹介したステップを参考に、少しずつ移行を進めてみてください。
関連する技術ブログ
可読性・再利用性・パフォーマンスを向上させる!フロントエンドリファクタリングの実践ガイド
フロントエンド開発におけるコードの品質を向上させるためのリファクタリング手法を徹底解説!命名規則や関数分割による可読性向上、共通処理のモジュール化、長大なコンポーネントの分割、設計パターンの適用など、実践的なアプローチを紹介します。さらに、Reactのパフォーマンス改善、非同期処理の最適化、依存関係の整理、TypeScriptの適用範囲拡大まで網羅し、段階的に進められるリファクタリング計画の策定方法も解説。よりメンテナブルで拡張しやすいコードを目指すエンジニア必見のガイドです!
shinagawa-web.com
ExpressとMongoDBで簡単にWeb APIを構築する方法【TypeScript対応】
本記事では、MongoDB Atlasを活用してREST APIを構築する方法を、初心者の方にも分かりやすいステップで解説します。プロジェクトの初期設定からMongoDBの接続設定、Expressを使用したルートの作成、さらにTypeScriptを用いた型安全な実装まで、実践的なサンプルコードを交えて丁寧に説明します。
shinagawa-web.com
Express(+TypeScript)入門ガイド: Webアプリケーションを素早く構築する方法
Node.jsでWebアプリケーションを構築するための軽量フレームワーク、Expressの基本的な使い方を解説。シンプルなサーバー設定からルーティング、ミドルウェアの活用方法、TypeScriptでの開発環境構築まで、実践的なコード例とともに学べます。
shinagawa-web.com
JestとTypeScriptで始めるテスト自動化:基本設定から型安全なテストの書き方まで徹底解説
JestとTypeScriptを使ったテスト自動化の基本を学びたい方へ。環境のセットアップ方法、型安全なテストを書くメリット、コードの信頼性を高める実践的なテクニックを初心者向けに丁寧に解説します。テストカバレッジの活用で、品質の高い開発を目指しましょう。
shinagawa-web.com
ESLint / Prettier 導入ガイド: Husky, CI/CD 統合, コード品質の可視化まで徹底解説
開発チームでコードの品質を統一するために、Linter(ESLint)と Formatter(Prettier)は欠かせません。Linter はコードの構文やスタイルの問題を検出し、Formatter はコードの整形を統一します。本記事では、9つの取り組みについて詳しく解説します。
shinagawa-web.com
フロントエンド開発に役立つモックサーバー構築:@graphql-tools/mock と Faker を使った実践ガイド
フロントエンド開発を先行させるために、バックエンドが未完成でもモックサーバーを立ち上げる方法を解説。@graphql-tools/mock と Faker を使用して、実際のデータに近い動作をシミュレートします。
shinagawa-web.com
Mock Service Worker (MSW) を使ったAPIモックとテストの効率化
MSW(Mock Service Worker)を使用して、フロントエンド開発やテスト環境でのAPIモックを効率的に行う方法を解説します。Mock Service Workerの基本的な使い方から、Jestテストでの活用方法、さらにテストを簡単にするための設定手順を紹介します。
shinagawa-web.com
Next.jsとAuth.jsで認証機能を実装するチュートリアル
Next.jsでアプリケーションを作る時に必要となる認証機能をどのように実装するかをご紹介する記事となります。アカウント登録から始まり、ログイン、ログアウト、ページごとのアクセス制御、OAuth、二要素認証、パスワードリセットなど認証に関連する様々な機能をコードベースでご紹介します。
shinagawa-web.com