TypeScript基礎講座 #1 はじまりとセットアップ

読了 9分

このシリーズは、JavaScriptはある程度わかるけれどTypeScriptは初めてという方のための入門講座です。全7回で構成されます。

  • #1 はじまりとセットアップ ← 今回
  • #2 基本の型
  • #3 interfaceとtype alias
  • #4 Union / Literal / Narrowing
  • #5 関数の型
  • #6 ジェネリクスを深く
  • #7 ユーティリティ型とtsconfig

今回は「なぜTypeScriptなのか」をはっきりさせ、初めてのコードを書いてコンパイルして実行するところまで進みます。

TypeScriptって何ですか? #

TypeScriptは、JavaScriptに型システムを加えた言語です。Microsoftが2012年に作り、今ではJavaScript系プロジェクトの事実上の標準になりました。React/Vue/Node.jsの大規模プロジェクトはほぼすべてTypeScriptで書かれています。

ひと言でまとめると、こうなります。

JavaScript + 型注釈 = TypeScript

JavaScriptで書けるすべてのコードはTypeScriptでも動作します。TypeScriptはその上に「この変数は文字列」「この関数は数値を受け取ってbooleanを返す」といった情報を表せる文法を加えたものです。

なぜ型を明示するのですか? #

JavaScriptは動的型付けの言語なので、変数がどの型なのかはコードを実行してみないとわかりません。小さなプログラムでは大きな問題になりませんが、コードが大きくなると次のようなことが頻繁に起こります。

動的型付けの落とし穴
function getDiscount(price, percent) {
  return price * percent / 100;
}

getDiscount(1000, 10);     // 100 — 正常
getDiscount('1000', 10);   // 1000... あれ? 文字列が来たのに動いてしまう
getDiscount(1000, '10');   // 100 — 動くけど意図通り?
getDiscount(1000);         // NaN — percentがundefined

JavaScriptは勝手に型を変換したりundefinedで埋めたりして、とりあえずコードを実行してしまいます。バグはずっと後になってから明るみに出るのです。

TypeScriptで同じ関数に型を明示すると、コードを書いている時点で間違った呼び出しを捕まえてくれます。

TypeScript版
function getDiscount(price: number, percent: number): number {
  return price * percent / 100;
}

getDiscount(1000, 10);     // ✓ OK
getDiscount('1000', 10);   // ✗ エラー: '1000'はnumberではありません
getDiscount(1000, '10');   // ✗ エラー
getDiscount(1000);         // ✗ エラー: 引数が足りません

エラーがコンパイル時に現れます。実行前にエディタで赤線が引かれ、ビルドも止まります。バグがユーザーに届く前に捕まるのです。

TypeScriptの価値をひと言で #

  • エディタの自動補完/推論 — オブジェクトにどんな属性があるか、関数が何を返すかをIDEが正確に教えてくれる
  • リファクタリングの安全性 — 関数のシグネチャを変えると、それを呼び出すすべての箇所が赤線で示される
  • ドキュメントの役割 — 型がそのまま関数の使い方の説明書になる
  • バグの事前防止undefined/nullのようなよくある落とし穴をコンパイラが防いでくれる

小さなトイプロジェクトでは負担になりますが、複数人で触るコードや、時間が経ってから読み返すコードでは、TypeScriptの価値が大きく光ります。

TypeScript vs JavaScript — 実行モデル #

ここで重要な事実があります。ブラウザとNode.jsはTypeScriptを直接実行できません。型注釈のような文法を知らないからです。

なので、私たちが書いた.tsファイルは実行前にJavaScriptに変換(コンパイル/トランスパイル)される必要があります。この変換を行うのがtsc(TypeScript Compiler)やesbuildswcViteのようなビルドツールです。

実行の流れ
.ts ファイル (作成)
   ↓ (コンパイル)
.js ファイル (実行可能)
ブラウザ / Node.js

興味深いのは、コンパイル結果の.jsファイルからは型情報がすべて消えているという点です。型は開発時にだけ意味があり、実行時にはただのJavaScriptなのです。だからTypeScriptは「ランタイムの追加コストなしに開発時の安全性を得る」ツールなのです。

最初のセットアップ — もっとも単純な方法 #

セットアップ方法はいくつかありますが、最初は一番単純な2つをおすすめします。

方法1. tscを直接使う(学習用) #

純粋にTypeScript自体を学ぶには最適です。

新しいフォルダを作る
mkdir ts-learn
cd ts-learn
npm init -y
npm install --save-dev typescript
npx tsc --init

各コマンドの役割は次のとおりです。

  • npm init -ypackage.jsonを生成(Node.jsプロジェクトにする)
  • npm install --save-dev typescript — TypeScriptコンパイラをインストール
  • npx tsc --init — 基本のtsconfig.jsonを生成

tsconfig.jsonはコンパイルオプションをまとめた設定ファイルです。詳しいオプションは#7で扱うので、今は作られたままにしておけば大丈夫です。

方法2. Viteで素早く(実務に近い) #

Web開発の文脈ならViteがコンパイル + 開発サーバーまでまとめて処理してくれて便利です。

Vite + TS プロジェクト
npm create vite@latest ts-learn
# Framework: Vanilla
# Variant: TypeScript
cd ts-learn
npm install
npm run dev

このシリーズは方法1で進めます。tscで直接コンパイルしてみると、TypeScriptが何をしているかが一番はっきり理解できるからです。後でVite/Webpackに移っても同じ原理です。

最初のコード #

ts-learn/フォルダにindex.tsファイルを作ります。

index.ts
function greet(name: string): string {
  return `こんにちは、${name}さん!`;
}

const message: string = greet('太郎');
console.log(message);

TypeScript固有の新しい文法は2か所です。

  • name: stringnameが文字列型であることを明示
  • (): string — 関数が文字列を返すことを明示
  • const message: string = ... — 変数messageが文字列であることを明示

コンパイルして実行する #

コンパイル
npx tsc

このコマンドを実行すると、同じフォルダにindex.jsファイルが生成されます(tsconfig.jsonのデフォルト次第で別の場所になることもあります)。そのファイルを開いてみてください。

コンパイル結果 index.js
function greet(name) {
    return "こんにちは、".concat(name, "さん!");
}
var message = greet('太郎');
console.log(message);

型注釈(: string)がすべて消えています。そしてES6文法(const、テンプレートリテラル)が少し古いJavaScriptに変換されているのが見えるはずです(古いブラウザサポートのためのデフォルト動作 — tsconfig.jsontargetオプションで調整可能、#7で扱います)。

これをNodeで実行してみましょう。

実行
node index.js
出力
こんにちは、太郎さん!

わざと間違える — 型エラーを実際に見る #

TypeScriptの価値を体感するには、実際に間違ってみるのが一番早いです。index.tsを次のように書き換えてみてください。

index.ts (間違いバージョン)
function greet(name: string): string {
  return `こんにちは、${name}さん!`;
}

const message: string = greet(42);  // 🚫 数値を入れた
console.log(message);

npx tscをもう一度実行すると、こうなります。

コンパイルエラー
index.ts:5:33 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

5 const message: string = greet(42);
                                ~~

Found 1 error in index.ts:5

TypeScriptが5行目33文字目に問題があると正確に指摘してくれます。.jsファイルも生成されません(デフォルト設定では — オプションで調整可能)。間違ったコードが実行段階まで流れていくのを防いでくれるのです。

VSCodeのようなエディタでは、npx tscを実行するより前に、すでに赤線で即座に表示されます。書いている間にリアルタイムで間違いを教えてくれるわけです。

自動推論 — 型を書かなくても推測してくれる #

すべての変数に型を明示する必要はありません。TypeScriptは賢く推論してくれます。

自動推論
const age = 30;             // age: number に自動推論
const name = '太郎';         // name: string
const isAdmin = true;       // isAdmin: boolean
const items = [1, 2, 3];    // items: number[]

age = 'hello';              // 🚫 エラー: numberにstringは入れられない

const age = 30と書くと、TypeScriptが自動でage: numberと推論します。その後age = 'hello'のように別の型を入れようとすると捕まえてくれます。

明示的な型注釈は、主に次のような場合に使います。

  • 関数の仮引数と戻り値の型(自動推論が弱い)
  • 意図を明確に表す(const id: string = computeId()のように可読性のため)
  • 空の変数に型だけ先に宣言(let user: User | null = null)

これからの記事を進める中で、このバランス感覚は徐々に身についていきます。

watch モード — 毎回コンパイルしないために #

毎回npx tscを打つのが面倒なら、watchモードがあります。

自動再コンパイル
npx tsc --watch

ファイルを修正するたびに自動でコンパイルされ、すぐに結果を受け取れます。学習中はこのモードをつけておくと便利です。

よくある落とし穴と神話 #

「TypeScriptは難しい」 #

最初の数日は不慣れですが、1〜2週間使えばJavaScriptに戻るときにかえって不安になるくらいになります。自動補完 / リファクタリング / 即時のエラーフィードバックの価値が大きいからです。

「TypeScriptは遅い」 #

実行はJavaScriptと同じです(ランタイムコストはゼロ)。コンパイル時間は追加されますが、incrementalビルドでほとんど体感しません。

「anyを使えばいいんじゃない?」 #

anyは型システムを事実上オフにする脱出口です。使いすぎるとTypeScriptの価値を失います。たまに必要なときだけ使い、普段はもっと正確な型を探すのがよいでしょう(#4のunion型など)。

「JSDocでも似たような効果が出せる」 #

そのとおりです。JSDocコメントで型を一部表現できます。ただし表現力とツールサポートではTypeScriptが圧倒的に強力です。

まとめ #

今回扱ったことを整理します。

  • TypeScript = JavaScript + 型注釈
  • コンパイル時に型エラーを捕まえ、実行時はただのJavaScript
  • npx tscでコンパイル、watchモードで自動再コンパイル
  • 型注釈は関数のシグネチャに優先して明示、変数は推論に任せることが多い
  • 自分で間違ったコードを書いてみてコンパイルエラーを受け取るのが最良の学習法

次回「TypeScript基礎講座 #2 基本の型」では、string/number/booleanから始めてarray、tuple、object、enumまで — 毎日使うことになる基本的な型を一つずつ見ていきます。

X