JavaScriptは動的型付けで、これは本当に便利です — 素早く反復し、自由にプロトタイプを作り、ビルドステップなしでデプロイできます。しかしその柔軟性にはコストがあります:エラーの全クラスが実行時にしか現れません。関数が文字列を期待し、undefinedを受け取り、アプリが本番環境で深夜2時にクラッシュします。TypeScript はJavaScriptの上に静的型システムを追加し、まさにそれらのエラーをコンパイル時に、エディタ内で、何かがデプロイされる前に検出します。この記事では、それが実際に何を意味するかを説明します — 実際のコード、正直なトレードオフ、誇張なし。

TypeScriptとは実際に何か

TypeScriptはJavaScriptのスーパーセットです:すべての有効な.jsファイルは.tsとしても有効です。ファイルの名前を変更し、型アノテーションをまったく追加しなくても、問題なくコンパイルできます。TypeScriptが追加するもの — 型アノテーション、インターフェース、ジェネリクス、列挙型 — はコンパイル時にすべて取り除かれます。出力は普通のJavaScriptです。TypeScriptは実行されるものを変えません;実行される前に知ることができることを変えます。

コンパイラはtscで、npm経由でインストールします。.tsファイルを指定すると、ランタイム(Node.js、ブラウザ、Deno)が実行できる.jsファイルを出力します。TypeScript固有の構文が本番環境に到達することは決してありません — 型は開発者とツールチェーンにのみ存在します。GitHubのTypeScriptリポジトリ自体が大規模なTypeScriptプロジェクトで、スケールの感覚を与えてくれます。

bash
# Compile a single file
npx tsc src/index.ts

# Compile a whole project (uses tsconfig.json)
npx tsc

# Watch mode — recompile on every save
npx tsc --watch

主な利点:実行時前のエラー

TypeScriptが最も目に見える形で解決する問題を示します。フィールドが文字列またはnullになる可能性があるAPIレスポンスを扱っているとします。普通のJavaScriptでは、これは本番環境でサイレントに失敗します:

js
// JavaScript — no error until this actually runs with a null value
function formatDisplayName(user) {
  return user.displayName.toUpperCase(); // 💥 TypeError if displayName is null
}

// This looks fine in isolation. The bug only appears when a user
// has no display name set — which might be rare, but it will happen.
const name = formatDisplayName({ displayName: null });

TypeScriptはコードが実行される前にこれを検出します:

ts
interface User {
  id: number;
  displayName: string | null;
  email: string;
}

function formatDisplayName(user: User): string {
  return user.displayName.toUpperCase();
  // ❌ TypeScript error: Object is possibly 'null'.
  //    Property 'toUpperCase' does not exist on type 'null'.
}

// Fix: handle the null case explicitly
function formatDisplayName(user: User): string {
  if (user.displayName === null) {
    return user.email; // fall back to email
  }
  return user.displayName.toUpperCase(); // TypeScript now knows this is safe
}

これがほとんどの開発者にとって「aha」の瞬間です。エラーメッセージは何が悪いのかとどこで悪いのかを正確に教えてくれます — エディタ内で、1行も実行する前に。strictモードを有効にすると、TypeScriptはそのstrictNullChecks機能を通じてnullundefinedの問題を検出するのに特に積極的で、strictモードではデフォルトで有効です。

内面化すべきパターン:TypeScriptのエラーはコンパイル時であり、実行時ではありません。tscからのエラーはコードに型の不整合があることを意味します — TypeScriptはそれを実行せずに失敗する(または失敗する可能性がある)ことを証明できます。型エラーを修正すれば、そのバグクラス全体を排除したことになります。

TypeScriptの型システム一覧

すべてにアノテーションを付ける必要はありません。TypeScriptは代入、戻り値、関数呼び出しから型を推論します — 多くの場合、普通のJavaScriptを書くだけで型チェックが無料で得られます。しかし、推論が不十分な場合のために、アノテーション構文を知っておくと役立ちます。

ts
// Primitives
const productName: string  = 'Wireless Keyboard';
const price:       number  = 79.99;
const inStock:     boolean = true;

// TypeScript infers these without annotations — same effect
const productName = 'Wireless Keyboard'; // inferred: string
const price       = 79.99;               // inferred: number

// Arrays
const tags:     string[]  = ['electronics', 'peripherals'];
const ratings:  number[]  = [4.5, 4.8, 4.2];

// Objects with interfaces
interface Product {
  id:          number;
  name:        string;
  price:       number;
  category:    string;
  inStock:     boolean;
  description: string | null; // union type — string or null
}

// Union types — a value that could be one of several types
type Status = 'active' | 'inactive' | 'pending'; // string literal union
type ID     = string | number;                    // string or number

// Function with typed parameters and return type
function formatProductCard(product: Product): string {
  const stockLabel = product.inStock ? 'In Stock' : 'Out of Stock';
  const desc       = product.description ?? 'No description available';
  return `${product.name} — $${product.price.toFixed(2)} (${stockLabel})
${desc}`;
}

// Generic function — works with any type, preserves it
function firstOrDefault<T>(items: T[], fallback: T): T {
  return items.length > 0 ? items[0] : fallback;
}

const first = firstOrDefault(['a', 'b', 'c'], 'z'); // inferred: string
const num   = firstOrDefault([1, 2, 3],       0);   // inferred: number

TypeScriptハンドブックは型システムを深く掘り下げています — 本当によく書かれており、基礎を習得したら読む価値があります。

プロジェクトにTypeScriptを設定する

TypeScriptをプロジェクトに追加するには約5分かかります。コンパイラを開発依存関係としてインストールし、設定ファイルを生成し、適切なペースで.jsファイルを.tsに名前変更し始めます。

bash
# Install TypeScript as a dev dependency
npm install -D typescript

# Generate tsconfig.json with sensible defaults
npx tsc --init

生成されたtsconfig.jsonには数十のオプションがあり、ほとんどはコメントアウトされています。知っておくべき主要なもの:

json
{
  "compilerOptions": {
    "target": "ES2020",       // what JavaScript version to output
    "module": "commonjs",     // module system (commonjs for Node, ESNext for bundlers)
    "outDir": "./dist",       // where compiled .js files go
    "rootDir": "./src",       // where your .ts source files live
    "strict": true,           // enables all strict type checks — highly recommended
    "esModuleInterop": true,  // smoother interop with CommonJS modules
    "skipLibCheck": true      // skip type checks on .d.ts files in node_modules
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

新しいプロジェクトでは常にstrict: trueを有効にしてくださいstrictNullChecksnoImplicitAnystrictFunctionTypesを含む複数のチェックをまとめます。既存のコードベースではこれらを段階的に有効にする必要があるかもしれません — しかしグリーンフィールドのものには、strictから始めてください。

TypeScript vs JavaScript — 本当のトレードオフ

TypeScriptには多くの真のメリットがありますが、実際のコストもあります。正直な内訳を示します:

  • 実際に機能するオートコンプリート。エディタはすべてのオブジェクトの形を知っているため、推測する代わりに正しいプロパティ名とメソッドシグネチャを提案できます。これだけで毎日時間を節約します。
  • 本番環境に到達する前に検出されるバグ。nullデリファレンス、間違った引数の型、必須プロパティの欠落 — TypeScriptはこれらを書き込み時に明らかにします、インシデントチャンネルの午前3時にではなく。
  • より安全なリファクタリング。インターフェースのフィールドを名前変更すると、TypeScriptは更新が必要なすべての呼び出し箇所をすぐに示します。大きなJavaScriptコードベースでは、そのような変更は恐ろしいものです。
  • コードが自己ドキュメント化される。sendEmail(to: string, subject: string, body: string, options?: EmailOptions): Promise<SendResult>のような関数シグネチャは、実装を読まなくても知る必要があるすべてを教えてくれます。
  • チームに適している。複数の開発者がコードベースを共有する場合、型はモジュール間の契約を形成します。同僚のコードを壊さずに自分のコードをリファクタリングできます。

そして正直なコスト:

  • ビルドステップが追加される。小さなスクリプトや素早いプロトタイプの場合、テストする前にtscを実行することは実際の摩擦です。バニラJavaScriptはすぐに実行されます。
  • 初期設定に時間がかかる。tsconfig.jsonの設定、サードパーティライブラリの型定義(@types/expressなど)の追加、エディタを適切に設定することは数時間の作業です。
  • anyがすべてを台無しにする。TypeScriptにはエスケープハッチがあります — 何でもanyとして型付けでき、そのことによってその値の型チェックが無効になります。anyの過度な使用はTypeScriptの摩擦を得るが安全性は得られないことを意味します。解決策ではなく、松葉杖です。
  • サードパーティライブラリの型が遅れる可能性がある。すべてのnpmパッケージがTypeScript定義を提供しているわけではありません。不完全または古い可能性のあるコミュニティが管理する@types/*パッケージに依存しているものもあります。
バニラJSで十分な場合:小さなCLIスクリプト、一回限りのデータ移行、捨てる予定の週末のプロトタイプ。TypeScriptの価値はプロジェクトサイズとチームサイズに比例して増加します。1人の開発者が使用する200行のスクリプトには型は必要ありません。8人の開発者が共有する5万行のコードベースには絶対に必要です。

TypeScriptが真に輝く場所

TypeScriptは3つの状況で最大の利益をもたらします:大規模なコードベース、共有ライブラリ、そして時間とともにリファクタリングされるもの。型システムはコメントやREADMEファイルとは異なり、常に最新の生きたドキュメントとして機能します。

実際の例:アプリがユーザーデータを返すREST APIを呼び出します。TypeScriptなしでは、APIが期待するものを返すと信頼しています。TypeScriptを使えば、レスポンスをモデル化し、何かが一致しない場合に即座にフィードバックを得られます:

ts
interface ApiUser {
  id:        number;
  username:  string;
  email:     string;
  avatarUrl: string | null;
  createdAt: string; // ISO 8601 date string
  role:      'admin' | 'editor' | 'viewer';
}

interface ApiResponse<T> {
  data:    T;
  total:   number;
  page:    number;
  perPage: number;
}

async function fetchUsers(page: number): Promise<ApiResponse<ApiUser[]>> {
  const response = await fetch(`/api/users?page=${page}`);
  if (!response.ok) {
    throw new Error(`Failed to fetch users: ${response.status}`);
  }
  return response.json() as Promise<ApiResponse<ApiUser[]>>;
}

// Now every consumer of this function knows exactly what they'll get
const result = await fetchUsers(1);
result.data.forEach(user => {
  // TypeScript knows user.role is 'admin' | 'editor' | 'viewer'
  // It will error if you try to access a property that doesn't exist
  console.log(`${user.username} (${user.role})`);
});

型チェックを一時的にオプトアウトする必要がある場合は、anyの代わりにunknownを使用してください。違い:anyはすべてのチェックをサイレントにバイパスしますが、unknownは値を使用する前に型を絞り込むことを強制します — 安全性を完全に失わずにエスケープハッチを得られます。

ts
// ❌ any — TypeScript trusts you completely, no checks
function processData(data: any) {
  data.nonExistentMethod(); // no error — TypeScript looks away
}

// ✅ unknown — you have to prove what it is before using it
function processData(data: unknown) {
  if (typeof data === 'string') {
    console.log(data.toUpperCase()); // safe — TypeScript knows it's a string here
  } else if (Array.isArray(data)) {
    console.log(data.length);        // safe — TypeScript knows it's an array
  }
}

TypeScript Playgroundはこれらのパターンを実験する最速の方法です。コードを貼り付け、コンパイルされたJavaScript出力と型エラーをリアルタイムで確認できます — インストール不要。

まとめ

TypeScriptは魔法ではなく、すべてのプロジェクトに適しているわけでもありません。しかし、成長している、1人以上の人がメンテナンスしている、またはリファクタリングされるJavaScriptコードベースには — 開発体験を本当に変えます。型システムはランタイムバグの全カテゴリをデプロイ前に検出し、オートコンプリートを本当に役立つものにし、リファクタリングをリスクの高い手動プロセスからツールチェーンが処理するものに変えます。

公式TypeScriptドキュメントから始めましょう — よく構造化されており初心者にも親切です。TypeScript Playgroundはセットアップなしで実験するのに最適です。MDNのTypeScriptグロサリーエントリは別の視点が欲しい場合の良い1ページ概要です。TypeScriptでJSONデータを扱っている場合、このサイトのJSON to TypeScriptツールは任意のJSONペイロードから直接TypeScriptインターフェースを生成します — 手で型を書かずにAPIレスポンスを素早くモデル化する際に便利です。