既存フロントにTypeScriptを段階導入する進め方
「このプロパティ、いつの間にか undefined が渡るようになっていた」「デプロイ後にコンソールに Cannot read property of undefined が出て初めて気づいた」——JavaScriptで書かれたフロントを2〜3年運用していると、こうした事故が徐々に増えてきます。動いてはいるが、直すたびに別の場所が壊れる不安がつきまとう状態です。
この記事では、既存のJavaScriptフロントにTypeScriptを段階的に導入する現実的な進め方を、発注者向けに整理します。結論から言えば、全面書き換えは避け、ファイル単位で少しずつ型を足していくのが最も事故が少なく予算対効果も高い進め方です。
なぜ「型なしJS」で変更事故が増えるのか
型のないコードベースでは、コンパイラが検出できるはずのバグが実行時まで発見されません。典型的な症状は以下の通りです。
- APIレスポンスの形が変わったのに、それを使っている画面側は実行するまでエラーに気づかない
- 関数の引数を1つ増やしたら、呼び出し側の一部だけ更新漏れが起きる
propsに渡すべき値が抜けていても、画面が真っ白になるかコンソールにエラーが出るまで誰も気づかない- リファクタリングのたびに「呼び出し箇所を全部grepで洗い出す」という手作業が発生し、漏れが事故になる
これらはコード品質の問題というより、変更の影響範囲をエディタとコンパイラが教えてくれないという構造的な問題です。TypeScriptを入れる目的は「型を書くこと」自体ではなく、この影響範囲の可視化にあります。
全面書き換えという罠
TypeScript導入の相談で最も多い誤解が「.js を全部 .ts にリネームして書き直す」という進め方です。これはほぼ確実に失敗します。理由は明確です。
- 数百ファイルを一度に
.ts化すると、型エラーが数百〜数千件同時に出る。優先順位がつけられず、途中で頓挫する - 書き換え期間中は機能追加が事実上止まる。ビジネス側の理解を得るのが難しく、途中で「元に戻す」判断が下されることもある
- 「今動いている挙動」を型定義に落とし込む過程で、仕様が曖昧な箇所の解釈がずれ、新たなバグを生む
- 書き換えが終わるまで型の恩恵を一切受けられない。投資対効果が出るのが数ヶ月〜半年後になる
全面書き換えは「きれいにしたい」という開発者側の欲求と相性がよく提案されがちですが、発注者にとっては投資回収が最も遅く、リスクが最も高い選択肢です。
段階導入の現実解
私たちが基本方針とするのは、プロダクトを動かしたまま少しずつ型を足していく段階導入です。手順は次の通りです。
1. allowJs で共存状態を作る
tsconfig.json で allowJs: true を設定すると、.js と .ts が同じビルドパイプラインの中で共存できます。これにより「全部書き換えてから初めて動く」状態を避け、1ファイル書き換えるごとに動作確認できる状態を最初に作れます。この共存期間は数ヶ月〜1年続くこともありますが、それで問題ありません。
2. ファイル単位で .ts / .tsx 化する
優先順位は「変更頻度が高い」「バグが集中している」「複数箇所から参照される共通処理」の順につけます。逆に「もう触る予定のない機能」は後回し、あるいは対象外にして構いません。全ファイルを型付けする必要はなく、触る場所から順に安全にするという考え方です。
- まずはAPIクライアント・共通ユーティリティなど、影響範囲が広く型の恩恵が大きい箇所から着手する
- 1ファイルを
.ts化するたびにビルドとテスト(あれば)を通し、grepで直接インポートしている箇所に型エラーが波及しないか確認する - UIコンポーネントは後回しにしがちだが、
propsの型付けは「渡し忘れ」バグの検出に直結するため優先度を上げるべき対象
3. any から始めて締めていく
移行初期は any を許容します。ここで完璧を求めると導入自体が止まります。目安として以下の順で進めます。
strict: falseかつany許容で.ts化を進める(まず動く状態を維持する)- 型付けが済んだファイルが増えてきたら、
noImplicitAnyを有効化し、暗黙のanyだけを潰す - コア機能(認証・決済・フォーム送信など事故が事業インパクトに直結する箇所)から
strict: true相当のチェックを個別に有効化する - 最終的にプロジェクト全体で
strict: trueを目指すが、期限は切らない。カバレッジが8割を超えたあたりから新規バグの検出効果が体感できるようになる
// @ts-ignore や any の残数は、grep -r "any" --include="*.ts*" src | wc -l のような形で機械的にカウントでき、進捗の可視化に使えます。感覚ではなく数値で追うことで、外注先の進捗報告が具体的になります。
4. 型定義の後付け
外部ライブラリやAPIレスポンスに型がない場合は、@types/* パッケージがあればそれを使い、なければ自前で .d.ts を書きます。ここで注意したいのは、API仕様書やOpenAPIスキーマがあるなら、そこから型を自動生成する選択肢です(openapi-typescript など)。手書きの型定義はAPI変更に追従できず、型があるのに実体とずれているという本末転倒な状態になりがちです。バックエンドとフロントを両方持つ受託先であれば、この自動生成の仕組みごと提案できるはずです。
導入で得られる効果と限界
段階導入がある程度進むと、次のような効果が体感できます。
- リファクタリング時に「型エラーが出た箇所」が影響範囲のチェックリストとして機能する。手作業のgrepに頼らなくなる
propsや関数引数の渡し忘れ・型の不一致がエディタ上(コンパイル前)で検出できる- 新しく参加したエンジニアが、型定義を読むだけで関数の入出力を把握できるようになり、オンボーディングが速くなる
一方で、TypeScriptが検出できないバグも当然あります。実行時の値(APIから返る実データが型定義と食い違う場合など)、ビジネスロジックの論理的な誤り、非同期処理のタイミング起因のバグは型では防げません。TypeScriptは変更事故を減らす手段の1つであり、テストや実装レビューを置き換えるものではないという前提は発注時に共有しておくべきです。
外注時の進め方
TypeScript段階導入を外部に依頼する際は、以下を確認してください。
- 全面書き換えを最初の提案にしていないか: allowJs から始める段階導入を説明できるかどうかは重要な判断材料です
- 優先順位の根拠を示せるか: 「なぜこのファイルから着手するのか」を変更頻度・バグ集中度などの根拠込みで説明できるか
anyの削減計画が具体的か: 「いつか strict にします」ではなく、ファイル単位・モジュール単位でのマイルストーンがあるか- 既存の挙動を壊さない確認方法があるか: テストがない場合、型付けと並行して最低限の回帰確認(手動チェックリストでも可)をどう用意するか
こうした段階的な進め方の考え方は、Reactのコンポーネント設計・state管理のリファクタリングとも地続きです。型のない巨大コンポーネントを抱えている場合はReactリファクタリングを外注すべき症状と進め方もあわせてご覧ください。また、そもそも既存プロダクトのどこから手を入れるべきかを見極める観点は既存プロダクトの改善、外部チームが最初に見る観点にまとめています。
torcheees では React / Next.js を中心としたフロントエンドの改善・モダナイゼーションを手がけています。詳しくはモダナイゼーションのサービスページをご覧ください。
まとめ
- 型なしJavaScriptでの変更事故は、コンパイラが検出できるはずのバグが実行時まで見つからない構造的な問題であり、TypeScriptは影響範囲を可視化する手段
- 全面書き換えは投資回収が遅くリスクが高い。allowJsで共存させながらファイル単位で
.ts化し、anyから始めてstrictへ締めていく段階導入が現実的 - TypeScriptはテストやレビューの代替ではなく、変更事故を減らす手段の1つという前提を発注時に共有しておくことが失敗しない選択につながる
「JavaScriptのままだと変更のたびに不安が拭えないが、全面書き換えをする余裕もない」という段階でも、torcheees はご相談を受け付けています。1〜4週間の「開発診断」で現状のコードを確認し優先順位をご提示するほか、継続的な改善支援も行っています。お問い合わせフォームからお気軽にご連絡ください。