メインコンテンツへスキップ

TypeScript開発者のための実践的ベストプラクティス2025

実際のプロジェクトで使えるTypeScriptのベストプラクティスを、型安全性、パフォーマンス、保守性の観点から解説します。

約5分で読了
Tech Blog
中級
TypeScript Node.js ESLint
TypeScript開発者のための実践的ベストプラクティス2025のイメージ画像

目次

TypeScript開発者のための実践的ベストプラクティス2025

TypeScriptは現在、JavaScript開発において欠かせない技術となっています。しかし、TypeScriptの真価を発揮するには、適切な使い方を理解することが重要です。

この記事では、実際のプロジェクトで使えるTypeScriptのベストプラクティスを、型安全性、パフォーマンス、保守性の観点から解説します。

基本的な型設計

1. Union Types vs Enums

良い例(Union Types)

type Status = 'pending' | 'approved' | 'rejected';

interface User {
  id: string;
  name: string;
  status: Status;
}

避けるべき例(Enums)

// 実行時にコードが生成される
enum Status {
  Pending = 'pending',
  Approved = 'approved',
  Rejected = 'rejected'
}

Union Typesは型レベルでのチェックのみで、実行時にコードを生成しないため、バンドルサイズが小さくなります。

2. 厳密な型定義

// ❌ 緩い型定義
interface ApiResponse {
  data: any;
  error?: string;
}

// ✅ 厳密な型定義
interface ApiResponse<T> {
  data: T | null;
  error: string | null;
  status: 'success' | 'error';
}

interface User {
  readonly id: string;
  name: string;
  email: string;
  createdAt: Date;
}

type ApiUserResponse = ApiResponse<User[]>;

高度な型テクニック

1. Mapped Types

// 既存の型から新しい型を生成
type Partial<T> = {
  [P in keyof T]?: T[P];
};

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

// 実用例
interface User {
  id: string;
  name: string;
  email: string;
}

type UserUpdate = Partial<User>; // すべてオプショナル
type UserCreate = Omit<User, 'id'>; // idを除外

2. Conditional Types

type NonNullable<T> = T extends null | undefined ? never : T;

type ApiResult<T> = T extends string 
  ? { message: T } 
  : T extends number 
  ? { code: T } 
  : { data: T };

// 使用例
type StringResult = ApiResult<string>; // { message: string }
type NumberResult = ApiResult<number>; // { code: number }
type UserResult = ApiResult<User>; // { data: User }

エラーハンドリング

1. Result Pattern

type Result<T, E = Error> = 
  | { success: true; data: T }
  | { success: false; error: E };

async function fetchUser(id: string): Promise<Result<User>> {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      return { success: false, error: new Error('User not found') };
    }
    const user = await response.json();
    return { success: true, data: user };
  } catch (error) {
    return { success: false, error: error as Error };
  }
}

// 使用例
const userResult = await fetchUser('123');
if (userResult.success) {
  console.log(userResult.data.name); // 型安全
} else {
  console.error(userResult.error.message);
}

2. Custom Error Types

abstract class AppError extends Error {
  abstract readonly code: string;
  abstract readonly statusCode: number;
}

class ValidationError extends AppError {
  readonly code = 'VALIDATION_ERROR';
  readonly statusCode = 400;
  
  constructor(
    message: string,
    public readonly field: string
  ) {
    super(message);
  }
}

class NotFoundError extends AppError {
  readonly code = 'NOT_FOUND';
  readonly statusCode = 404;
}

パフォーマンス最適化

1. 型のキャッシュ

// ❌ 毎回計算される
type ExpensiveType<T> = T extends string ? ComplexOperation<T> : never;

// ✅ キャッシュされる
type CachedExpensiveType<T> = T extends string 
  ? T extends infer U 
    ? U extends string 
      ? ComplexOperation<U> 
      : never 
    : never 
  : never;

2. 遅延型評価

// 必要時のみ型を計算
type LazyType<T> = T extends any ? ComputeType<T> : never;

開発体験の向上

1. TypeScript設定

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitOverride": true
  }
}

2. ESLint設定

// eslint.config.js
export default [
  {
    rules: {
      '@typescript-eslint/no-explicit-any': 'error',
      '@typescript-eslint/prefer-nullish-coalescing': 'error',
      '@typescript-eslint/prefer-optional-chain': 'error',
      '@typescript-eslint/no-unused-vars': 'error'
    }
  }
];

実用的なユーティリティ型

// Deep Readonly
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

// Pick by Value Type
type PickByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

// Example
interface User {
  id: string;
  name: string;
  age: number;
  isActive: boolean;
}

type StringFields = PickByType<User, string>; // { id: string; name: string }
type NumberFields = PickByType<User, number>; // { age: number }

まとめ

TypeScriptの効果的な活用には:

  1. 型安全性の追求anyの使用を避け、厳密な型定義
  2. パフォーマンス意識:型計算コストの考慮
  3. 開発体験:適切な設定とツールの活用
  4. 保守性:理解しやすい型設計

これらを意識することで、より良いTypeScriptコードを書けるようになります。

TypeScriptは単なる型付きJavaScriptではなく、設計を支援する強力な言語です。型システムを理解し、適切に活用することで、バグの少ない保守しやすいコードを書けるようになります。

この記事をシェア

Tech Blog