既存プロジェクトに TypeScript を入れたい。ただし開発は止められない。
このときに使っている段階的な移行手順です。
原則:一度に厳しくしない
最初から strict: true にすると、数百件のエラーが出て手が止まります。
エラーゼロの状態を保ったまま、少しずつ締めていきます。
{
"compilerOptions": {
"allowJs": true, // .js を許可(これが最重要)
"checkJs": false, // まだ .js はチェックしない
"strict": false,
"noEmit": true
}
}
allowJs により、既存の .js を1行も触らずに TypeScript の設定を導入できます。
段階1:新しく書くファイルだけ .ts にする
既存コードには触りません。新規ファイルのみ .ts で書きます。
これだけで、数か月後には型のあるコードが自然に増えていきます。
段階2:よく使われる型を先に定義する
APIレスポンスなど、多くの場所から参照されるものから型を作ります。
export interface Order {
id: number;
status: "pending" | "paid" | "cancelled";
total: number;
createdAt: string; // ISO8601
}
ステータスをユニオン型にしておくと、
タイプミスがコンパイル時に落ちるようになります。効果が大きい割に安価です。
段階3:影響範囲の小さいファイルから .ts に変える
依存されていない末端のファイル(ユーティリティなど)から変換します。
中心にあるファイルを最初に変えると、影響が全体に波及して止まります。
# どこから参照されているか調べてから決める
grep -rn "from './utils/format'" src/ | wc -l
段階4:徐々に厳しくする
エラーがゼロになったら、1つずつ有効化します。
"noImplicitAny": true, // まずこれ
"strictNullChecks": true, // 次にこれ(効果が最大)
strictNullChecks は最も多くのバグを見つけますが、
最も多くのエラーも出ます。他が片付いてからにします。
any を許容する場所を決める
現実には any を残さざるを得ない箇所が出ます。
放置ではなく、意図を書いて残すようにしています。
// TODO(2023-03): レガシーAPIのレスポンス。型定義を起こす
const legacy = res.data as any;
検索できる印を付けておくと、後で棚卸しできます。
まとめ
allowJsで既存コードを触らずに導入する- 新規ファイルだけ
.tsにする - 共通の型(APIレスポンス)から定義する
- 末端のファイルから変換する
noImplicitAny→strictNullChecksの順で締める
「エラーゼロの状態を維持したまま進む」のが、途中で挫折しないための条件でした。