TypeScriptとは何か?

TypeScriptは、MicrosoftがオープンソースとしてリリースしたJavaScriptのスーパーセット(上位互換言語)です。通常のJavaScriptコードはそのままTypeScriptとして動作しますが、TypeScriptでは「静的型付け」という強力な機能が追加されています。

JavaScriptは動的型付け言語で、変数の型は実行時に決まります。これは柔軟ですが、大規模プロジェクトでは「型の不一致」によるバグが発生しやすいという欠点があります。TypeScriptはコンパイル時に型エラーを検出し、バグを事前に防ぎます。

💡 TypeScriptはブラウザでは直接動作しません。コンパイルによってJavaScriptに変換されてから実行されます。

TypeScriptのメリット

  • 型安全性:型エラーをコンパイル時に検出できる
  • IDE支援:VS Codeなどで強力な補完・リファクタリング機能が使える
  • 可読性向上:コードの意図が型定義によって明確になる
  • 大規模開発に適している:チーム開発でのミスを減らせる
  • 最新JS機能:TypeScriptコンパイラが古いJSへのダウンコンパイルを担当

TypeScriptのインストールと基本設定

まずNode.jsとnpmが必要です。インストール後、以下のコマンドでTypeScriptをインストールします:

# グローバルインストール
npm install -g typescript

# バージョン確認
tsc --version

プロジェクトの初期化は tsconfig.json で行います:

# tsconfig.json を生成
tsc --init

生成された tsconfig.json の主要な設定項目:

{
  "compilerOptions": {
    "target": "ES2020",        // コンパイル後のJSバージョン
    "module": "commonjs",      // モジュール形式
    "strict": true,            // 厳格モード(推奨)
    "outDir": "./dist",        // 出力先ディレクトリ
    "rootDir": "./src"         // ソースファイルの場所
  }
}

基本的な型の使い方

TypeScriptの型は変数宣言の後に : 型名 のように記述します(型アノテーション)。ただし、TypeScriptは多くの場合、型を自動的に推論できます。

プリミティブ型

// string型
let name: string = "田中太郎";
let greeting = "こんにちは";  // 型推論でstring型

// number型
let age: number = 25;
let price: number = 3.14;

// boolean型
let isActive: boolean = true;

// null / undefined
let empty: null = null;
let notSet: undefined = undefined;

// any型(型チェックを無効化 - 非推奨)
let anything: any = "文字列でも数値でもOK";
anything = 42;  // エラーにならない

配列型

// 配列の型指定(2通りの書き方)
let numbers: number[] = [1, 2, 3, 4, 5];
let strings: Array<string> = ["apple", "banana", "cherry"];

// タプル型(要素数と各型が固定)
let person: [string, number] = ["田中", 25];
// person[0] は string、person[1] は number

オブジェクト型

// インラインでオブジェクト型を定義
let user: { name: string; age: number; email?: string } = {
  name: "山田花子",
  age: 30
  // email は ? がついているのでオプション(省略可能)
};

// type エイリアスを使う方法
type User = {
  name: string;
  age: number;
  email?: string;
};

const newUser: User = { name: "鈴木", age: 28 };
💡 ? をつけると「オプショナルプロパティ」になります。省略可能なフィールドに使います。

インターフェース(Interface)

インターフェースはオブジェクトの「形(shape)」を定義するための仕組みです。type エイリアスと似ていますが、拡張(extends)実装(implements)に向いています。

// インターフェース定義
interface Animal {
  name: string;
  age: number;
  speak(): string;  // メソッドシグネチャ
}

// インターフェースの実装
const dog: Animal = {
  name: "ポチ",
  age: 3,
  speak() {
    return "ワン!";
  }
};

// インターフェースの拡張
interface Pet extends Animal {
  owner: string;
}

const cat: Pet = {
  name: "タマ",
  age: 2,
  speak() { return "ニャー!"; },
  owner: "田中"
};

クラスへの実装

interface Drawable {
  draw(): void;
  color: string;
}

class Circle implements Drawable {
  color: string;
  radius: number;

  constructor(color: string, radius: number) {
    this.color = color;
    this.radius = radius;
  }

  draw(): void {
    console.log(`${this.color}の円を描画(半径:${this.radius}`);
  }

  area(): number {
    return Math.PI * this.radius ** 2;
  }
}

const circle = new Circle("赤", 5);
circle.draw();  // "赤の円を描画(半径:5)"
💡 type vs interface:ほとんどの場合はどちらでも使えますが、クラスのimplementsや将来的に拡張する場合は interface を、ユニオン型など複雑な型合成には type を使うのが一般的です。

関数の型定義

TypeScriptでは関数の引数と戻り値に型を指定できます。これにより、間違った使い方をすぐに検出できます。

// 引数と戻り値の型を指定
function add(a: number, b: number): number {
  return a + b;
}

// アロー関数
const multiply = (x: number, y: number): number => x * y;

// オプショナルパラメータ
function greet(name: string, prefix?: string): string {
  return `${prefix ?? "こんにちは"}, ${name}!`;
}

greet("田中");           // "こんにちは, 田中!"
greet("田中", "おはよう");  // "おはよう, 田中!"

// デフォルト値
function createUser(name: string, role: string = "user") {
  return { name, role };
}

// 戻り値なし(void型)
function logMessage(msg: string): void {
  console.log(msg);
}

ユニオン型とインターセクション型

// ユニオン型(AまたはB)
type StringOrNumber = string | number;

function formatId(id: StringOrNumber): string {
  if (typeof id === "number") {
    return id.toString().padStart(6, "0");
  }
  return id;
}

formatId(42);       // "000042"
formatId("AB123");  // "AB123"

// リテラル型(特定の値のみ許可)
type Direction = "north" | "south" | "east" | "west";
type Status = "pending" | "active" | "inactive";

function move(dir: Direction, steps: number): void {
  console.log(`${dir}へ${steps}歩進む`);
}

move("north", 3);  // OK
// move("up", 1); // エラー!"up" は Direction に含まれない

ジェネリクス(Generics)

ジェネリクスは「型を引数として受け取る」仕組みです。様々な型に対応しつつも型安全を保てる、TypeScriptの強力な機能です。

// ジェネリック関数
function identity<T>(value: T): T {
  return value;
}

identity<string>("こんにちは");  // 型を明示
identity(42);                    // 型推論でnumber

// 配列の最初の要素を返す
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

first([1, 2, 3]);        // number | undefined
first(["a", "b", "c"]);  // string | undefined

ジェネリックインターフェース

// APIレスポンスの型定義
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

interface User {
  id: number;
  name: string;
}

// 使用例
const userResponse: ApiResponse<User> = {
  data: { id: 1, name: "田中" },
  status: 200,
  message: "OK"
};

const usersResponse: ApiResponse<User[]> = {
  data: [{ id: 1, name: "田中" }, { id: 2, name: "鈴木" }],
  status: 200,
  message: "OK"
};

型制約(extends)

// T は length プロパティを持つ型に制限
function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength("hello");      // 5(string)
getLength([1, 2, 3]);    // 3(array)
// getLength(42);        // エラー!numberはlengthを持たない
💡 ジェネリクスは「型の変数」です。Tの代わりにUKVなども使われます(慣習的に)。

実践的なTypeScriptのTips

型ガード(Type Guards)

ユニオン型の値の型を実行時に絞り込む手法です。

type Cat = { type: "cat"; meow(): void };
type Dog = { type: "dog"; bark(): void };
type Pet = Cat | Dog;

function makeSound(pet: Pet): void {
  if (pet.type === "cat") {
    pet.meow();  // ここではCat型として扱われる
  } else {
    pet.bark();  // ここではDog型として扱われる
  }
}

// instanceof を使った型ガード
function processInput(input: string | Date): string {
  if (input instanceof Date) {
    return input.toISOString();
  }
  return input.toUpperCase();
}

Readonly と as const

// Readonly でプロパティを読み取り専用に
interface Config {
  readonly apiUrl: string;
  readonly timeout: number;
}

const config: Config = {
  apiUrl: "https://api.example.com",
  timeout: 3000
};
// config.apiUrl = "変更"; // エラー!

// as const でオブジェクト全体を定数に
const COLORS = {
  primary: "#e8a040",
  secondary: "#f0c060",
  danger: "#e05040"
} as const;

// COLORS.primary は "e8a040" のリテラル型になる

Mapped Types と Utility Types

interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

// Partial: 全プロパティをオプショナルに
type PartialProduct = Partial<Product>;
// { id?: number; name?: string; ... }

// Required: 全プロパティを必須に
type RequiredProduct = Required<PartialProduct>;

// Pick: 一部のプロパティだけ取り出す
type ProductSummary = Pick<Product, "id" | "name" | "price">;

// Omit: 一部のプロパティを除外
type ProductWithoutId = Omit<Product, "id">;

// Record: キーと値の型を指定
type CategoryMap = Record<string, Product[]>;
⚠️ any型の多用はTypeScriptの恩恵を損ないます。型がわからない場合は unknown を使い、型ガードで絞り込む方法を検討しましょう。

まとめ

TypeScriptの基礎から実践的なテクニックまで解説しました。重要なポイントを振り返りましょう:

  • TypeScriptはJavaScriptのスーパーセットで、静的型付けによって安全なコードが書ける
  • 型アノテーション型推論を活用して、必要な箇所だけ型を明示的に指定する
  • インターフェースでオブジェクトの形を定義し、クラスに実装できる
  • ジェネリクスで型安全な汎用コンポーネントを作れる
  • ユーティリティ型(Partial、Pick、Omitなど)で型の変換が簡単にできる

最初は型の記述が面倒に感じることもありますが、プロジェクトが大きくなるほどTypeScriptの恩恵を実感できます。まずは既存のJavaScriptプロジェクトに tsconfig.json を追加するところから始めてみましょう!