TypeScriptを小さく始める——型を付ける順番は「境界」から
既存のJavaScriptをいきなり全部書き換えなくても、TypeScriptの恩恵は受けられます。私が小さな案件で採っている順番、JSDocでのチェック、外から来るデータの型付け、strictの上げ方を書きます。
TypeScriptは「導入するか、しないか」の二択で考えると重くなります。私は まず今のJavaScriptのままチェックだけ効かせる、次に外から入ってくるデータに型を付ける、最後に設定を厳しくしていく という順番で進めています。全部のファイルを .ts に書き換えるのは、その後で十分です。
一歩目:拡張子を変えずに、チェックだけ効かせる
いちばん手前の一歩は、既存の .js ファイルの1行目に // @ts-check と書くことです。TypeScriptの公式ハンドブックでは、これでJavaScriptファイルでも型のエラーを出せると説明されています。推論できない箇所は、JSDocのコメントで型を補えます(TypeScript: JS Projects Utilizing TypeScript)。
// @ts-check
/**
* @param {number} price
* @param {number} rate
*/
function withTax(price, rate) {
return Math.round(price * (1 + rate));
}
withTax("1200", 0.1); // ← エディタ上でエラーになる
ビルドの仕組みは何も変わりません。VS Codeのようにエディタが型チェックに対応していれば、その場で赤線が出ます。私はこの段階を「チームに負担をかけずに、型の便利さを体感してもらう期間」として使っています。ファイル単位で始められるので、触る機会の多い共通処理から1つずつ付けていけば足ります。
二歩目:型を付けるのは「境界」から
次に、どこから型を付けるか。私は データが外から入ってくる場所 を最優先にしています。APIのレスポンス、CMSから取ってくる記事、フォームの入力値、localStorage の中身。この「境界」で形を決めておくと、その先の処理はほぼ推論で追えるようになります。
type NewsItem = {
id: string;
title: string;
url: string;
publishedAt: string;
};
async function getNews(): Promise<NewsItem[]> {
const res = await fetch("/api/news");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
ここで大事なのは、型を書くこと自体より「このAPIはこういう形で返ってくる」という約束が文字になることです。ディレクションの目線で見ても、NewsItem のような型はそのまま仕様書の一部になります。項目が増えた・名前が変わった、という話が出たときに、影響する箇所をエディタが教えてくれるのは、一人で往復している身にはかなり助かります。
ただし、型はあくまで「こうであるはず」という宣言で、実行時にデータを検査してくれるわけではありません。外部サービスのように形が変わりうる相手なら、受け取った直後に最低限の項目があるかを確かめる処理を別に書いておきます。
逆に、後回しにしていいのは、ファイルの中で完結する小さな関数や、一時変数です。ここは推論に任せても困る場面が少ないので、最初から細かく書くと手間のわりに得るものが少ないと感じています。
三歩目:strictは最初から、ただし外し方を知っておく
新規のプロジェクトなら、私は tsconfig.json の strict を最初から true にします。公式リファレンスでは、strict をオンにすると「strict mode family」と呼ばれる一連のチェックがまとめて有効になり、そのうえで個別に外せると説明されています(TSConfig Reference: strict)。
既存のコードを移行する場合は事情が違います。いきなり strict にするとエラーが数百件出て、手が止まりがちです。私は次の順番で上げています。
| 段階 | やること |
|---|---|
| 1 | strict をオンにし、エラーの多い個別チェックだけ一時的に外す |
| 2 | 外したチェックを1つずつ戻し、そのたびにエラーを潰す |
| 3 | 全部戻せたら、例外設定を消す |
同じページには、将来のバージョンで strict 配下に新しいチェックが加わる可能性があり、アップデートで新しいエラーが出ることがある、とも書かれています。TypeScriptのバージョンを上げるときは、ビルドが通るかを確かめる時間を見積もりに入れておくと安心です。
実行環境との付き合い方
最近は、ビルドを挟まずに .ts を動かせる環境も増えています。Node.jsの公式ドキュメントでは、型注釈を取り除いて実行する「type stripping」がv22.18.0/v23.6.0から既定で有効になったとされています。一方で、Node.jsは型を空白に置き換えるだけで、型チェックは行わないとも明記されています(Node.js: Modules: TypeScript)。
つまり「動く」ことと「型が正しい」ことは別です。私は小さなスクリプトならNode.jsでそのまま動かしつつ、型の確認はエディタと tsc --noEmit に任せる、と役割を分けています。
明日から試せる一手
手元のプロジェクトで、APIやCMSからデータを受け取っている関数を1つ選び、そのファイルの1行目に // @ts-check を足してください。そのうえで、受け取るデータの形をJSDocの @typedef で1つ書いてみる。赤線が出た箇所が、これまで「たまたま動いていた」ところです。そこを直すだけでも、型を付ける順番の感覚がつかめるはずです。