LESSON 10

ポートフォリオを
TypeScript にしよう

Lesson 9 で、JavaScript を使ってポートフォリオに動きを付けました。今回はそのスクリプトを、 現場でとてもよく使われている TypeScript(タイプスクリプト)に書き換えます。 見た目も動きも同じまま、「間違いに気づきやすいコード」に生まれ変わらせるのがゴールです。

1. TypeScriptとは

TypeScript は、Microsoft が開発した、JavaScript に「型(かた)」というルールを足した言語です。 JavaScript の書き方はほぼそのまま使えて、そこに「この箱には何を入れるか」というラベルを書き足していきます。

JavaScript の箱

どんなものでも入れられる、ラベルのない箱。数字を入れていた箱に、うっかり文字を入れても何も言われません。

JavaScript
let angle = 0;
angle = "ごど";   // 文字を入れても、何も言われない

TypeScript の箱

「数字専用」とラベルを貼った箱。数字以外を入れようとすると、その場で「それは入れられません」と教えてくれます。

TypeScript
let angle: number = 0;
angle = "ごど";   // ← エラー!「数字専用」の箱です

: number の部分が型です。キッチンの調味料の容器に「砂糖」「塩」とラベルを貼っておけば、取り違えを防げますよね。型はそのラベルのようなものです。

型意味例
number数字0、5、3.14
string文字(文字列)"こんにちは!"
booleanはい/いいえtrue、false
string[]文字のリスト(配列)["A", "B", "C"]

2. 何がうれしいの?

JavaScript では、間違いに気づくのはブラウザで動かしてみたときです。しかも、エラーにならずに「なんだか変な動き」になるだけのこともあります。 TypeScript なら、コードを書いている最中に、VS Code が赤い波線で間違いを教えてくれます。

JavaScript の場合

書く → 保存 → ブラウザで確認 → 「あれ、動かない……」→ コンソールで原因を探す

TypeScript の場合

書いた瞬間に VS Code が「ここ、おかしいですよ」と赤い波線で教えてくれる

数百行のポートフォリオなら JavaScript でも困りませんが、何十人もの PG が何万行ものコードを書く実際のシステム開発では、この差がとても大きくなります。 そのため、フロントエンドエンジニアの現場では、今や TypeScript で書くのが当たり前になりつつあります。

3. ブラウザは TypeScript を読めない

実は、ブラウザが理解できるのは JavaScript だけです。そのため、TypeScript で書いたファイルは、 コンパイラ(翻訳ツール)で JavaScript に変換してから使います。この変換をコンパイルと呼びます。

script.tsTypeScript で書く
(型のラベル付き)
→
tsc(コンパイラ)型のチェックをして、
JavaScript に変換
→
script.jsブラウザが
読み込んで動かす
型のラベルは「書くときの安全装置」なので、変換後の JavaScript からは取り除かれます。index.html は、これまでどおり script.js を読み込みます。

英語の原稿を翻訳者にチェック・翻訳してもらってから、日本語版を印刷するイメージです。翻訳者(tsc)は、原稿の間違い(型のミス)も指摘してくれます。

4. 準備:Node.js と TypeScript を入れる

コンパイラ tsc を使うために、Node.js(ブラウザの外で JavaScript を動かす仕組み)と、 その付属ツール npm(便利なツールをダウンロードする仕組み)を使います。 会社のパソコンにインストールするときは、Lesson 6 と同じく社内のルールに従ってください。

  1. Node.js をインストールする

    Chrome で https://nodejs.org/ を開き、LTS(長期サポート版)の Windows 用インストーラーをダウンロードして実行します。 基本的には「Next」で進めて大丈夫です。途中の「Tools for Native Modules」のチェックは入れなくてかまいません。

  2. インストールできたか確認する

    VS Code をいったん閉じて開き直し、ターミナルで次の2つを実行します。それぞれバージョン番号が表示されれば成功です。

    ターミナル
    node -v
    npm -v
  3. portfolio フォルダを npm 用に準備する

    VS Code で portfolio フォルダを開いた状態で、ターミナルに次のコマンドを入力します。package.json というファイルができます。

    ターミナル
    npm init -y
  4. TypeScript をインストールする

    ターミナル
    npm install --save-dev typescript

    node_modules というフォルダができ、その中に TypeScript のコンパイラがダウンロードされます。このフォルダの中身は触らなくて大丈夫です。

  5. 設定ファイル tsconfig.json を作る

    portfolio フォルダに新しいファイル tsconfig.json を作り、次の内容を書きます。「どのファイルを、どんなルールで変換するか」の設定です。

    tsconfig.json
    {
      "compilerOptions": {
        "target": "ES2020",
        "lib": ["ES2020", "DOM"],
        "strict": true
      },
      "files": ["script.ts"]
    }
    設定意味
    "target": "ES2020"どのバージョンの JavaScript に変換するか
    "lib": ["ES2020", "DOM"]document など、ブラウザの機能を使えるようにする
    "strict": true型のチェックを厳しくする(間違いを見つけやすくなる)
    "files": ["script.ts"]変換するファイル
うまくいかないときは確認すること
node や npm が見つからないと言われるNode.js のインストール後に、VS Code を開き直したか確認してください。
「このシステムではスクリプトの実行が無効になっているため…」と出る PowerShell の安全設定によるものです。VS Code のターミナル右上の「+」の横の「∨」から「Command Prompt」を選んでターミナルを開き直し、同じコマンドを実行してみてください。 それでもだめな場合は、情報システム担当者に相談してください。

5. script.js を TypeScript に書き換える

まずはファイル名を変えるだけ

VS Code で script.js を右クリック →「名前の変更」で、script.ts に変えます(.ts は TypeScript の印)。 すると、さっそくいくつかの行に赤い波線が出るはずです。波線の上にマウスを乗せると、こんなメッセージが表示されます。

VS Code に表示されるメッセージ
'greeting' は 'null' の可能性があります。

これは、「getElementById("greeting") で探しても、見つからないかもしれないよね? 見つからなかったら(null=空っぽ)、 greeting.textContent の行でエラーになりますよ」という指摘です。 JavaScript では、名札の名前を打ち間違えても、ブラウザで動かすまで気づけませんでした。TypeScript は、その可能性を書いている時点で教えてくれるわけです。

① あいさつ:「必ずある」と伝える、型を書く

script.ts(① あいさつ)
// ① 時間帯に合わせて、あいさつを変える
const greeting = document.getElementById("greeting") as HTMLElement;
const hour: number = new Date().getHours();

as は「私が保証します」という約束なので、本当に HTML に id="greeting" があるかは自分で確認しておく必要があります。 本格的な開発では if (greeting) { … } のように「あるときだけ動かす」書き方もよく使われます。

② 鉄棒:箱にラベルを貼る

script.ts(② 鉄棒)
// ② 鉄棒の人が、くるくる回り続ける
const person = document.getElementById("person") as HTMLImageElement;
const tetsubo = document.getElementById("tetsubo") as HTMLElement;
let angle: number = 0;
let moving: boolean = true;

setInterval(function (): void {
  if (moving) {
    angle = angle + 5;
    person.style.transform = "rotate(" + angle + "deg)";
  }
}, 30);

tetsubo.addEventListener("click", function (): void {
  moving = !moving;
});

③④ コントローラーとひとこと:同じ処理を「関数」にまとめる

Lesson 9 では、③ と ④ の両方で「ランダムに番号を選ぶ」処理(Math.floor(Math.random() * …))を書いていました。 せっかくなので、これを1つの関数にまとめて、型も付けてみましょう。script.ts の一番上に書きます。

script.ts(一番上に追加)
// 0〜(length - 1) のどれかの番号を、ランダムに選ぶ
function pickRandomIndex(length: number): number {
  return Math.floor(Math.random() * length);
}

あとは、③ と ④ でこの関数を使うように書き換えます。

script.ts(③ コントローラー)
// ③ コントローラーのボタンが、ポチポチ光る
const padButtons = document.querySelectorAll(".pad-button");

setInterval(function (): void {
  const padButton = padButtons[pickRandomIndex(padButtons.length)];
  padButton.classList.add("pressed");
  setTimeout(function (): void {
    padButton.classList.remove("pressed");
  }, 200);
}, 350);
script.ts(④ ひとこと)
// ④ ボタンを押すと、ひとことがランダムに変わる
const messages: string[] = [
  "最近はエアロピクルスも好きだよ",
  "スーの歌も好きだよ",
  "ねずみなんだけど丑年なんでスー"
];
const message = document.getElementById("message") as HTMLElement;
const messageButton = document.getElementById("message-button") as HTMLButtonElement;

messageButton.addEventListener("click", function (): void {
  message.textContent = messages[pickRandomIndex(messages.length)];
});
関数+型の組み合わせが強い理由: pickRandomIndex("4") のように数字ではなく文字を渡したり、返ってきた番号を文字の箱に入れようとしたりすると、TypeScript がすぐに教えてくれます。 関数は「たくさんの場所から使われる部品」なので、使い方のルール(型)が書いてあると、他の人が使うときにも迷いません。

書き換えが終わった script.ts の全体はこちらです。

script.ts
// 0〜(length - 1) のどれかの番号を、ランダムに選ぶ
function pickRandomIndex(length: number): number {
  return Math.floor(Math.random() * length);
}

// ① 時間帯に合わせて、あいさつを変える
const greeting = document.getElementById("greeting") as HTMLElement;
const hour: number = new Date().getHours();

if (hour < 10) {
  greeting.textContent = "おはようございます!";
} else if (hour < 18) {
  greeting.textContent = "こんにちは!";
} else {
  greeting.textContent = "こんばんは!";
}

// ② 鉄棒の人が、くるくる回り続ける
const person = document.getElementById("person") as HTMLImageElement;
const tetsubo = document.getElementById("tetsubo") as HTMLElement;
let angle: number = 0;
let moving: boolean = true;

setInterval(function (): void {
  if (moving) {
    angle = angle + 5;
    person.style.transform = "rotate(" + angle + "deg)";
  }
}, 30);

tetsubo.addEventListener("click", function (): void {
  moving = !moving;
});

// ③ コントローラーのボタンが、ポチポチ光る
const padButtons = document.querySelectorAll(".pad-button");

setInterval(function (): void {
  const padButton = padButtons[pickRandomIndex(padButtons.length)];
  padButton.classList.add("pressed");
  setTimeout(function (): void {
    padButton.classList.remove("pressed");
  }, 200);
}, 350);

// ④ ボタンを押すと、ひとことがランダムに変わる
const messages: string[] = [
  "最近はエアロピクルスも好きだよ",
  "スーの歌も好きだよ",
  "ねずみなんだけど丑年なんでスー"
];
const message = document.getElementById("message") as HTMLElement;
const messageButton = document.getElementById("message-button") as HTMLButtonElement;

messageButton.addEventListener("click", function (): void {
  message.textContent = messages[pickRandomIndex(messages.length)];
});

6. JavaScript に変換して動かす

  1. コンパイルする

    ターミナルで次のコマンドを実行します。npx は「このフォルダにインストールしたツールを実行する」ためのコマンドです。

    ターミナル
    npx tsc

    何も表示されずに終われば成功です(エラーがあると、ここで表示されます)。portfolio フォルダに、変換された script.js ができています。

  2. ブラウザで確認する

    index.html を Chrome で開きます。index.html は何も変えていません。 これまでどおり script.js を読み込むので、鉄棒もコントローラーもひとことも、Lesson 9 と同じように動けば成功です。

できあがった script.js の最初のほうを見てみると、: number や as HTMLElement などの型の部分が、きれいに消えているのがわかります。

script.js(コンパイルで作られたファイルの一部)
"use strict";
// 0〜(length - 1) のどれかの番号を、ランダムに選ぶ
function pickRandomIndex(length) {
    return Math.floor(Math.random() * length);
}
// ① 時間帯に合わせて、あいさつを変える
const greeting = document.getElementById("greeting");
const hour = new Date().getHours();
注意: これからは script.ts の方を編集します。script.js はコンパイルのたびに上書きされるので、直接書き換えても消えてしまいます。 script.ts を直したら、そのたびに npx tsc を実行しましょう。

毎回コマンドを打つのが面倒なら

次のコマンドを実行しておくと、script.ts を保存するたびに自動でコンパイルしてくれます(止めるときは Ctrl + C)。

ターミナル
npx tsc --watch

さらに、package.json の "scripts" を次のように書き換えておくと、npm run build(1回変換)・npm run watch(自動で変換)という短い名前で実行できます。 多くの開発現場で使われている書き方です。

package.json("scripts" の部分)
  "scripts": {
    "build": "tsc",
    "watch": "tsc --watch"
  },

Lesson 9 のときと比べて、フォルダの中身はこう変わりました。

As-Is(Lesson 9 まで)
portfolio/
├── index.html
├── style.css
├── script.js   ← 自分で書く JavaScript
└── images/
To-Be(Lesson 10 から)
portfolio/
├── index.html
├── style.css
├── script.ts       ← 自分で書く TypeScript
├── script.js       ← tsc が自動で作る(編集しない)
├── tsconfig.json   ← TypeScript の設定
├── package.json    ← npm の設定
├── node_modules/   ← ダウンロードしたツール(触らない)
└── images/

オレンジの script.js が、緑の script.ts に置き換わりました。

7. わざと間違えてみよう

TypeScript のありがたみを体験するために、script.ts の ② の部分を、わざと次のように書き換えてみてください。

script.ts(② の中を、わざと間違える)
    angle = angle + "5";

すぐに VS Code が赤い波線を出し、npx tsc を実行すると次のエラーが表示されます。

ターミナル
script.ts(26,5): error TS2322: Type 'string' is not assignable to type 'number'.

VS Code の日本語表示では「型 'string' を型 'number' に割り当てることはできません。」と表示されます。(26,5) は「26行目の5文字目」という意味です(行番号は書き方によって変わります)。

「数字専用の箱(angle)に、文字("5")をくっつけたものは入れられません」という意味です。 JavaScript だと、エラーにならずに 0 と "5" がくっついて "05" という文字になり、 「なぜか鉄棒の人の回り方がおかしい……」という原因のわかりにくいバグになってしまいます。

確認できたら、angle = angle + 5; に戻しておきましょう。

Claude Code にも聞いてみよう

型のエラーは、慣れるまでメッセージが難しく感じます。そんなときは Claude Code に聞いてみましょう。

Claude Code への質問の例
script.ts で「'greeting' は 'null' の可能性があります。」というエラーが出ました。
どういう意味か、初心者にもわかるように説明してください。直し方は2通り教えてください。

確認クイズ

選択肢をクリックしてください。

Q1. TypeScript は、JavaScript に何を足した言語?

Q2. let moving = true; の moving に付ける型は?

Q3. ブラウザで TypeScript のコードを動かすには、どうする?

Q4. TypeScript にしたあと、プログラムを直したいときに編集するファイルは?

Q5. TypeScript を使うと、型の間違いにいつ気づける?

まとめ