コース目次 / 第1章
TSとの出会い
リネームするだけでTypeScriptになります。型はまず「推論されている」ことを確かめます。
経験者の方へ: JavaScriptに自信がある方は、Aパートは流し読みで構いません。事故が起きるところと、TSにするところから精読してください。
今日のゴール
ヘッダーに「こんにちは、はるとです。次の旅まであと24日」と出ます。
今日つかまえるバグ: 数を数える関数に、うっかり文字を渡してしまう事故。JavaScriptはこれを何も言わずに通します。
Aパート: JSで書く
src/main.js を次のように書き換えます。
import './style.css'; -console.log('旅ダッシュボード、起動しました');+const TRIP_DATE = '2026-09-19';++function daysUntil(dateText) {+ const target = new Date(dateText);+ const today = new Date();+ const diff = target.getTime() - today.getTime();+ return Math.ceil(diff / (1000 * 60 * 60 * 24));+}++function greet(name, days) {+ return `こんにちは、${name}です。次の旅まであと${days}日`;+}++const greeting = document.querySelector('#greeting');+greeting.textContent = greet('はると', daysUntil(TRIP_DATE));行頭の + - は変更の目印です。コピーすると、記号と削除された行を除いたこの章を終えた時点の内容が入ります。
`こんにちは、${name}さん` のようなバッククォートの文字列を テンプレートリテラル と呼びます。+ でつなぐより読みやすいので、このコースではこちらを使います。
Date の引き算は今日の主役ではありません。コピーでOKです。
保存すると、画面に挨拶とカウントダウンが出ます。ここまでがAパートです。
事故を踏む
いま書いた daysUntil に、日付ではなく数字を渡してみてください。
greeting.textContent = greet('はると', daysUntil(2026));画面には 次の旅まであと NaN日 と出ます。NaN は「数ではない」という意味の、JavaScriptの困った返事です。エラーは1つも出ません。 これがJavaScriptの静かな壊れ方です。
もう1つ試します。ブラウザの開発者ツールのConsoleで "3" + 4 と打つと、答えは 7 ではなく "34" です。数と文字を足すと、JavaScriptは黙って文字としてつなげます。
壊した行は元に戻してから、次へ進んでください。
Bパート: TSにする
いよいよリネームです。
VS Code をお使いの方
左側のファイル一覧で main.js を選び、F2キー を押して main.ts に変えます。
ターミナル派の方
プロジェクトのフォルダで、名前を変えます。
行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。
mv は PowerShell でもそのまま使えます(Move-Item の別名です)。
そして index.html の1行だけ直します。ここを忘れると画面が真っ白になります。
</main> - <script type="module" src="/src/main.js"></script>+ <script type="module" src="/src/main.ts"></script> </body> </html>行頭の + - は変更の目印です。コピーすると、記号と削除された行を除いたこの章を終えた時点の内容が入ります。
TypeScriptを入れる
行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。
バージョンを指定しているのには理由があります。 TypeScriptは今も更新が続いていて、新しい版では別の設定が必要になることがあります。教材と同じ版を入れておけば、教材と同じ画面になります。新しい版を試すのは、コースを一度終えてからで十分です。
もう1つ、src/vite-env.d.ts という名前のファイルを作って、次の1行だけ書きます。
/// <reference types="vite/client" />これは「import './style.css' のようなVite独自の書き方を知っておいてください」とTypeScriptに教える1行です。意味は分からなくて大丈夫です。これが無いと、CSSを読み込んでいる行が「そんなファイルは知らない」と怒られます。
そしてプロジェクトの一番上に tsconfig.json を作ります。今日読むのは3行だけです。全部の行は第10章で読みます。
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"allowJs": true,
"noEmit": true
},
"include": ["src"]
}"strict": true… 検査をいちばん厳しくする。最初からONにします。あとから締めると、自分のコードが途中の章で突然赤くなって心が折れます。"allowJs": true….jsのファイルが混ざっていても怒らない。このコースの生命線です。毎章、Aパートでは.jsが1つ混ざるからです。"include": ["src"]…srcフォルダの中だけを見る。
package.json に検査コマンドを足します。
"scripts": { "dev": "vite",+ "check": "tsc --noEmit", "build": "vite build", "preview": "vite preview" }, "devDependencies": {+ "typescript": "~5.9.2", "vite": "^6.3.5" }行頭の + - は変更の目印です。コピーすると、記号と削除された行を除いたこの章を終えた時点の内容が入ります。
行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。
エラーが4つ出ます。それが正常です
src/main.ts(5,20): error TS7006: Parameter 'dateText' implicitly has an 'any' type.
src/main.ts(12,16): error TS7006: Parameter 'name' implicitly has an 'any' type.
src/main.ts(12,22): error TS7006: Parameter 'days' implicitly has an 'any' type.
src/main.ts(17,1): error TS18047: 'greeting' is possibly 'null'.びっくりしないでください。これで合っています。
このあと4つを0にします。この章の本体は、その「4→0」の作業そのものです。エラーの数が減っていくのを見るのが、今日いちばん大事な体験です。
エラーの読み方も、ここで型を作っておきましょう。読むのは、ファイル名・行番号・error の右のひとことだけです。src/main.ts(5,20) なら「main.ts の5行目20文字目」。それだけ分かれば、見に行けます。
実行する係と、検査する係
ここで、このコースでいちばん大事な仕組みを1つ。
実行するのはVite、検査するのはエディタと npm run check。この2人は別人です。
Viteは型を剥がして捨てるだけで、正しいかどうかは見ません。だから赤い波線が出ていても画面は動きます。逆に、赤線を消しても画面が直るとは限りません。
「赤いのに動く」「直したのに動かない」で混乱しないよう、最初に覚えてください。
書き足していないのに、もう動いてはいる
ここで大事な区別を1つ。画面は、さっきから何も変わらず動き続けています。リネームしても、赤線が4つ出ていてもです。
TypeScriptは JavaScriptのスーパーセット(上位互換)です。これは「JavaScriptとして書いたものは、そのままTypeScriptとしても動く」という意味です。「検査に通る」という意味ではありません。動くことと、検査を通ることは別です。第1章でこの2つを分けて覚えると、この先ずっと楽になります。
そして、型を1文字も書いていないのに、TypeScriptはもう型を知っています。const greeting = ... の greeting にマウスを乗せてみてくださいカーソルを置いて K を押してみてください(Neovim で LSP を設定している場合)。型が表示されます。これが 型推論 です。出てこなくても、そのまま進んで構いません。型が合っているかを確かめる本体は、このあと使う npm run check のほうです。注釈は、推論が届かないところにだけ書きます。
4つのエラーを0にする
上から1個ずつ片付けます。
最初の3つは、どれも同じ TS7006 です。関数の引数だけは、TypeScriptにも中身が推し量れません。Parameter 'dateText' implicitly has an 'any' type =「この子が何者か教えて」。だから引数にだけ注釈を書きます。
4つ目の TS18047: 'greeting' is possibly 'null' は種類が違います。document.querySelector('#greeting') は、その部品が無かったときに null を返すので、TypeScriptは「無いかもしれないのに触るんですか」と心配しているのです。これには次の節で答えます。
import './style.css'; const TRIP_DATE = '2026-09-19'; -function daysUntil(dateText) {+function daysUntil(dateText: string): number { const target = new Date(dateText); const today = new Date(); const diff = target.getTime() - today.getTime(); return Math.ceil(diff / (1000 * 60 * 60 * 24)); } -function greet(name, days) {+function greet(name: string, days: number): string { return `こんにちは、${name}です。次の旅まであと${days}日`; } -const greeting = document.querySelector('#greeting');+const greeting = document.querySelector('#greeting')!; greeting.textContent = greet('はると', daysUntil(TRIP_DATE));行頭の + - は変更の目印です。コピーすると、記号と削除された行を除いたこの章を終えた時点の内容が入ります。
ロジックの行は1行も動いていません。足したのは : string : number と、最後の行の ! だけです。TS化の差分はいつもこの読み方をします。
もう一度 npm run check を打ってください。今度は何も出ません。4つが0になりました。 TypeScriptの世界では「何も言われない = 全部OK」です。
そして最初の事故を、もう一度やってみてください。
greeting.textContent = greet('はると', daysUntil(2026));今度は 書いた瞬間に赤い波線 が出ます。Argument of type 'number' is not assignable to parameter of type 'string'。画面を見に行く前に、机の上で捕まりました。
! という仮免許(4つ目のエラーの答え)
さっきの TS18047: 'greeting' is possibly 'null' を消したのが、行末に付いた ! です。
document.querySelector('#greeting')! の最後の ! は、「ここには絶対ある、と私が保証します」という印です。本当は「無いかもしれない」ので、TypeScriptは心配していました。
! は仮免許です。いまは使ってよいことにしますが、第10章で卒業式をします(自分で安全な関数を書いて、! を全部消します)。使ってよい場所はこの1箇所だけにしてください。
わざと壊してみる
greet の呼び出しを次のように書き換えてください。
greeting.textContent = greet(24, 'はると');ここで赤くなるのが正解です。 引数の順番が逆だと、TypeScriptは2つとも指摘します。エラーが複数出ても慌てないでください。上から1個だけ直します。 読むのは1行目とファイル名だけで十分です。確認したら元に戻します。
あなたの番
- 名前を自分の名前に変えてください。
TRIP_DATEを、自分がいちばん楽しみにしている予定の日に変えてください。旅行でなくても構いません。- 合格条件: 変えたあとも
npm run checkがエラー0。
実務メモ
React Nativeでも、Metro(実行係)は型エラーがあっても動きます。最後の砦はCIで回す tsc --noEmit です。今日の npm run check は、その砦を自分の手元に置いたものです。
TSエラー語辞典
この章で実際に出るエラー英文と、その日本語訳です。卒業するころには全章ぶんがお助けページに集まります。
| エラー英文 | 言っていること |
|---|---|
Parameter 'x' implicitly has an 'any' type |
xが何者か教えてください(注釈が要る場所の合図) |
'greeting' is possibly 'null' |
それは無いかもしれません。あると保証するか、確かめてください |
Type 'number' is not assignable to type 'string' |
そこは文字列の席です |
ハマったら
リネームしたら画面が真っ白になった
index.html の <script type="module" src="/src/main.js"> が main.js のままです。main.ts に直してください。この章でいちばん多いつまずきです。
VS Codeが .ts を認識してくれない
コマンドパレット(Macは command + shift + P、Windowsは Ctrl + Shift + P)を開いて「Reload Window」を実行してください。これがこのコースの定型リカバリ2番です。
エディタが .ts を認識してくれない
リネームの直後は、エディタが古いファイル名のまま検査係(LSP)を掴んでいることがあります。ファイルをいったん閉じて開き直してください(Neovim なら :bd で閉じてから nvim src/main.ts)。これがこのコースの定型リカバリ2番です。
それでも表示が古いままでも、npm run check が通っていれば問題ありません。検査の本体はそちらです。
ZIPで戻したい
このページの下には2つのZIPがあります。JS版はAパート(JSで動いた状態)まで、TS版は章末(TS化まで終わった状態)です。詰まった場所に合わせて選んでください。
解凍したら、フォルダの中で次の2つを順に打ちます。
行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。
丸ごと置き換えると自分で書き換えた内容が消えます。 まず src/places.ts(自分の行き先データ)と、自分で変えた文言をメモ帳に待避してから、壊れたファイルだけを差し替えてください。
こうなっていればOK
うまくいかないときは、この章のファイル一式をダウンロードして続きから進めても大丈夫です。JS版(TS化する前)とTS版(章末)の2つがあります。
あと5章で、あなたのサイトに公開URLが付きます。
この章はまだ完了していません。