TypeScript完全ガイド


TypeScriptとは

TypeScriptは、Microsoftが開発したJavaScriptのスーパーセット(上位互換言語)です。JavaScriptに静的型付けを追加し、大規模なアプリケーション開発をより安全で効率的に行えるように設計されています。

TypeScriptで書かれたコードは、最終的にJavaScriptにコンパイル(トランスパイル)され、あらゆるJavaScript環境で実行できます。これにより、既存のJavaScriptコードとの互換性を保ちながら、型安全性の恩恵を受けることができます。

特徴

1. 静的型付け

TypeScriptの最大の特徴は静的型付けシステムです。変数、関数の引数、戻り値などに型を指定することで、コンパイル時にエラーを検出できます。

2. JavaScriptとの互換性

すべての有効なJavaScriptコードは、そのままTypeScriptコードとして動作します。既存のJavaScriptプロジェクトを段階的にTypeScriptに移行できます。

3. 高度な型システム

  • ジェネリクス
  • ユニオン型
  • インターセクション型
  • 条件付き型
  • マップ型
  • テンプレートリテラル型

4. 優れた開発体験

  • IntelliSenseによる強力な自動補完
  • リアルタイムエラー検出
  • リファクタリング支援
  • 豊富なエディタサポート

5. 最新のJavaScript機能

ES2015以降の最新JavaScript機能を古いブラウザでも使用できるよう、適切なJavaScriptにコンパイルします。

歴史

2012年:初回リリース

  • Microsoft社のAnders Hejlsberg(C#の開発者)が主導
  • JavaScript開発における型安全性の課題を解決する目的で開発開始

2014年:TypeScript 1.0

  • 最初の安定版リリース
  • ジェネリクスとモジュールシステムの導入

2015年:TypeScript 1.5

  • ES6機能のサポート開始
  • decorator(実験的機能)の追加

2016年:TypeScript 2.0

  • null/undefined安全性の強化
  • ユニオン型の改善

2018年:TypeScript 3.0

  • プロジェクト参照機能の追加
  • 条件付き型の導入

2020年:TypeScript 4.0

  • 可変長タプル型
  • ラベル付きタプル要素

2023年:TypeScript 5.0

  • デコレーターの正式サポート
  • const型パラメータ

現在

  • 活発な開発が継続中
  • 大規模なエコシステムとコミュニティ

環境構築と使用方法

インストール

グローバルインストール

npm install -g typescript

プロジェクト単位でのインストール

npm install --save-dev typescript

TypeScriptコンパイラの使用

基本的なコンパイル

tsc filename.ts

設定ファイルを使用したコンパイル

tsc --init  # tsconfig.jsonを生成
tsc         # tsconfig.jsonの設定に基づいてコンパイル

tsconfig.json設定例

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

基本的な型システム

プリミティブ型

// 基本的な型
let name: string = "田中";
let age: number = 25;
let isActive: boolean = true;
let data: undefined = undefined;
let empty: null = null;

// 配列
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ["Alice", "Bob"];

// タプル
let person: [string, number] = ["田中", 25];

オブジェクト型

// オブジェクト型の定義
type User = {
  id: number;
  name: string;
  email?: string; // オプショナルプロパティ
  readonly createdAt: Date; // 読み取り専用
};

const user: User = {
  id: 1,
  name: "田中太郎",
  createdAt: new Date()
};

関数型

// 関数の型定義
function greet(name: string): string {
  return `Hello, ${name}!`;
}

// アロー関数
const add = (a: number, b: number): number => a + b;

// 関数型
type Calculator = (a: number, b: number) => number;

const multiply: Calculator = (a, b) => a * b;

高度な型機能

ジェネリクス

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

// ジェネリッククラス
class Container<T> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }
}

// 使用例
const stringContainer = new Container<string>("Hello");
const numberContainer = new Container<number>(42);

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

// ユニオン型
type Status = "loading" | "success" | "error";

function handleStatus(status: Status) {
  switch (status) {
    case "loading":
      console.log("読み込み中...");
      break;
    case "success":
      console.log("成功しました");
      break;
    case "error":
      console.log("エラーが発生しました");
      break;
  }
}

// インターセクション型
type Name = { name: string };
type Age = { age: number };
type Person = Name & Age;

const person: Person = {
  name: "田中",
  age: 30
};

条件付き型

// 条件付き型
type NonNullable<T> = T extends null | undefined ? never : T;

type Example1 = NonNullable<string | null>; // string
type Example2 = NonNullable<number | undefined>; // number

// マップ型
type Partial<T> = {
  [P in keyof T]?: T[P];
};

type Required<T> = {
  [P in keyof T]-?: T[P];
};

フレームワーク

React with TypeScript

TypeScriptはReactと非常に相性が良く、コンポーネントの型安全性を提供します。

// React コンポーネントの例
import React, { useState } from 'react';

interface Props {
  title: string;
  initialCount?: number;
}

const Counter: React.FC<Props> = ({ title, initialCount = 0 }) => {
  const [count, setCount] = useState<number>(initialCount);

  return (
    <div>
      <h2>{title}</h2>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
};

export default Counter;

Vue.js with TypeScript

Vue 3ではTypeScriptのサポートが大幅に改善されています。

// Vue コンポーネントの例
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'TodoItem',
  props: {
    todo: {
      type: Object as () => {
        id: number;
        text: string;
        completed: boolean;
      },
      required: true
    }
  },
  setup(props, { emit }) {
    const isEditing = ref(false);
    const editText = ref(props.todo.text);

    const toggleEdit = () => {
      isEditing.value = !isEditing.value;
    };

    const saveTodo = () => {
      emit('update', {
        ...props.todo,
        text: editText.value
      });
      isEditing.value = false;
    };

    return {
      isEditing,
      editText,
      toggleEdit,
      saveTodo
    };
  }
});

Node.js with TypeScript

サーバーサイド開発でもTypeScriptは広く使用されています。

// Express.js with TypeScript
import express, { Request, Response } from 'express';

interface UserRequest extends Request {
  body: {
    name: string;
    email: string;
  };
}

const app = express();
app.use(express.json());

app.post('/users', (req: UserRequest, res: Response) => {
  const { name, email } = req.body;
  
  // バリデーション
  if (!name || !email) {
    return res.status(400).json({
      error: 'Name and email are required'
    });
  }

  // ユーザー作成処理
  const user = { id: Date.now(), name, email };
  
  res.status(201).json(user);
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

実践的なコーディング例

1. APIクライアント

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

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

// APIクライアントクラス
class ApiClient {
  private baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  async get<T>(endpoint: string): Promise<ApiResponse<T>> {
    const response = await fetch(`${this.baseUrl}${endpoint}`);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return response.json();
  }

  async post<T, U>(endpoint: string, data: T): Promise<ApiResponse<U>> {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return response.json();
  }
}

// 使用例
const client = new ApiClient('https://api.example.com');

async function fetchUser(id: number): Promise<User> {
  const response = await client.get<User>(`/users/${id}`);
  return response.data;
}

2. 状態管理

// 状態管理のための型定義
interface TodoState {
  todos: Todo[];
  filter: 'all' | 'active' | 'completed';
  loading: boolean;
}

interface Todo {
  id: number;
  text: string;
  completed: boolean;
  createdAt: Date;
}

// アクション型
type TodoAction =
  | { type: 'ADD_TODO'; payload: { text: string } }
  | { type: 'TOGGLE_TODO'; payload: { id: number } }
  | { type: 'DELETE_TODO'; payload: { id: number } }
  | { type: 'SET_FILTER'; payload: { filter: TodoState['filter'] } }
  | { type: 'SET_LOADING'; payload: { loading: boolean } };

// リデューサー
function todoReducer(state: TodoState, action: TodoAction): TodoState {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [
          ...state.todos,
          {
            id: Date.now(),
            text: action.payload.text,
            completed: false,
            createdAt: new Date(),
          },
        ],
      };

    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload.id
            ? { ...todo, completed: !todo.completed }
            : todo
        ),
      };

    case 'DELETE_TODO':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload.id),
      };

    case 'SET_FILTER':
      return {
        ...state,
        filter: action.payload.filter,
      };

    case 'SET_LOADING':
      return {
        ...state,
        loading: action.payload.loading,
      };

    default:
      return state;
  }
}

3. 非同期処理とエラーハンドリング

// 結果型の定義
type Result<T, E = Error> = 
  | { success: true; data: T }
  | { success: false; error: E };

// 非同期処理のラッパー関数
async function tryAsync<T>(
  asyncFn: () => Promise<T>
): Promise<Result<T>> {
  try {
    const data = await asyncFn();
    return { success: true, data };
  } catch (error) {
    return { 
      success: false, 
      error: error instanceof Error ? error : new Error(String(error))
    };
  }
}

// 使用例
async function fetchUserSafely(id: number): Promise<Result<User>> {
  return tryAsync(async () => {
    const response = await fetch(`/api/users/${id}`);
    
    if (!response.ok) {
      throw new Error(`Failed to fetch user: ${response.statusText}`);
    }
    
    return response.json();
  });
}

// 呼び出し側
async function handleUserFetch(id: number) {
  const result = await fetchUserSafely(id);
  
  if (result.success) {
    console.log('User data:', result.data);
  } else {
    console.error('Error:', result.error.message);
  }
}

ベストプラクティス

1. 型定義の分離

// types/user.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

export interface CreateUserRequest {
  name: string;
  email: string;
}

export interface UpdateUserRequest {
  name?: string;
  email?: string;
}

2. 厳密な型チェック設定

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noImplicitReturns": true,
    "noImplicitThis": true
  }
}

3. 型ガード関数の活用

// 型ガード関数
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'name' in obj &&
    'email' in obj
  );
}

// 使用例
function processUserData(data: unknown) {
  if (isUser(data)) {
    // この時点でdataはUser型として扱われる
    console.log(`User: ${data.name} (${data.email})`);
  }
}

4. Utility Typesの活用

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

// 一部のプロパティのみを持つ型
type PublicUser = Omit<User, 'password'>;

// 一部のプロパティを選択する型
type UserPreview = Pick<User, 'id' | 'name'>;

// すべてのプロパティをオプショナルにする型
type PartialUser = Partial<User>;

// すべてのプロパティを必須にする型
type RequiredUser = Required<User>;

まとめ

TypeScriptは、JavaScriptの柔軟性を保ちながら型安全性を提供する強力な言語です。大規模なアプリケーション開発において、以下のような利点をもたらします:

  • 開発時のエラー検出: コンパイル時にエラーを発見できるため、実行時エラーを大幅に減らせます
  • コードの可読性向上: 型注釈により、コードの意図が明確になります
  • リファクタリングの安全性: 型システムにより、変更の影響範囲を正確に把握できます
  • チーム開発の効率化: 型定義により、チームメンバー間でのコードの理解が容易になります

TypeScriptを習得することで、より堅牢で保守性の高いWebアプリケーションを開発できるようになります。既存のJavaScriptプロジェクトでも段階的に導入できるため、実際のプロジェクトで少しずつ活用していくことをお勧めします。

この記事は役に立ちましたか?

もし参考になりましたら、下記のボタンで教えてください。

コメント

この記事へのコメントはありません。