Back to Blog

TypeScript入門:JavaScriptからの移行ガイド

Minh Duy
Published on January 11, 2025
5 min read
TypeScript入門:JavaScriptからの移行ガイド
Web Development
TypeScript
JavaScript
プログラミング
Web開発

TypeScript入門:JavaScriptからの移行ガイド

TypeScriptは、Microsoftが開発したJavaScriptのスーパーセットです。静的型付けを追加することで、より安全で保守性の高いコードを書くことができます。

TypeScriptとは?

TypeScriptは以下の特徴を持っています:

  • 静的型付け: コンパイル時にエラーを検出
  • ES6+サポート: 最新のJavaScript機能をサポート
  • 優れたIDE支援: IntelliSenseとリファクタリング機能
  • 段階的導入: 既存のJavaScriptプロジェクトに段階的に導入可能

基本的な型システム

プリミティブ型

// 基本的な型
let name: string = "田中";
let age: number = 25;
let isStudent: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;

// 配列
let numbers: number[] = [1, 2, 3, 4, 5];
let names: Array<string> = ["田中", "佐藤", "鈴木"];

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

オブジェクト型

// インターフェース


// 使用例
const user: User = {
  id: 1,
  name: "田中太郎",
  email: "tanaka@example.com",
  createdAt: new Date()
};

// 型エイリアス
type Status = "pending" | "approved" | "rejected";
type UserWithStatus = User & { status: Status };

関数型

// 関数の型定義
function greet(name: string): string {
  return `こんにちは、${name}さん!`;
}

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

// オプショナルパラメータ
function createUser(name: string, age?: number): User {
  return {
    id: Math.random(),
    name,
    email: `${name}@example.com`,
    age,
    createdAt: new Date()
  };
}

// デフォルトパラメータ
function multiply(a: number, b: number = 1): number {
  return a * b;
}

// レストパラメータ
function sum(...numbers: number[]): number {
  return numbers.reduce((total, num) => total + num, 0);
}

高度な型機能

ジェネリクス

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

const stringResult = identity<string>("hello");
const numberResult = identity<number>(42);

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




const userResponse: ApiResponse<UserData> = {
  data: { id: 1, name: "田中" },
  status: 200,
  message: "成功"
};

// ジェネリック制約


function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength("hello"); // OK
logLength([1, 2, 3]); // OK
// logLength(123); // エラー:numberにはlengthプロパティがない

ユニオン型と交差型

// ユニオン型
type StringOrNumber = string | number;

function formatValue(value: StringOrNumber): string {
  if (typeof value === "string") {
    return value.toUpperCase();
  } else {
    return value.toString();
  }
}

// 交差型




type PersonEmployee = Person & Employee;

const employee: PersonEmployee = {
  name: "田中",
  age: 30,
  employeeId: "EMP001",
  department: "開発部"
};

条件付き型とマップ型

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

type StringType = NonNullable<string | null>; // string
type NumberType = NonNullable<number | undefined>; // number

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

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



type PartialUser = Partial<User>; // すべてのプロパティがオプショナル
type RequiredUser = Required<User>; // すべてのプロパティが必須

実践的な使用例

React with TypeScript





const ContactForm: React.FC<Props> = ({ title, onSubmit }) => {
  const [formData, setFormData] = useState<FormData>({
    name: '',
    email: '',
    message: ''
  });

  const [errors, setErrors] = useState<Partial<FormData>>({});

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    
    // バリデーション
    const newErrors: Partial<FormData> = {};
    if (!formData.name) newErrors.name = '名前は必須です';
    if (!formData.email) newErrors.email = 'メールアドレスは必須です';
    
    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors);
      return;
    }

    onSubmit(formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>{title}</h2>
      
      <div>
        <input
          type="text"
          placeholder="名前"
          value={formData.name}
          onChange={(e) => setFormData({ ...formData, name: e.target.value })}
        />
        {errors.name && <span className="error">{errors.name}</span>}
      </div>
      
      <div>
        <input
          type="email"
          placeholder="メールアドレス"
          value={formData.email}
          onChange={(e) => setFormData({ ...formData, email: e.target.value })}
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>
      
      <div>
        <textarea
          placeholder="メッセージ"
          value={formData.message}
          onChange={(e) => setFormData({ ...formData, message: e.target.value })}
        />
      </div>
      
      <button type="submit">送信</button>
    </form>
  );
};

export default ContactForm;

API クライアント

// API レスポンスの型定義




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

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

  async get<T>(endpoint: string): Promise<ApiResponse<T>> {
    try {
      const response = await fetch(`${this.baseUrl}${endpoint}`);
      const data = await response.json();
      
      if (!response.ok) {
        return {
          success: false,
          error: {
            code: response.status.toString(),
            message: data.message || 'エラーが発生しました'
          }
        };
      }

      return {
        success: true,
        data
      };
    } catch (error) {
      return {
        success: false,
        error: {
          code: 'NETWORK_ERROR',
          message: 'ネットワークエラーが発生しました'
        }
      };
    }
  }

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

      const data = await response.json();
      
      if (!response.ok) {
        return {
          success: false,
          error: {
            code: response.status.toString(),
            message: data.message || 'エラーが発生しました'
          }
        };
      }

      return {
        success: true,
        data
      };
    } catch (error) {
      return {
        success: false,
        error: {
          code: 'NETWORK_ERROR',
          message: 'ネットワークエラーが発生しました'
        }
      };
    }
  }
}

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



async function fetchUser(id: number): Promise<User | null> {
  const response = await apiClient.get<User>(`/users/${id}`);
  
  if (response.success && response.data) {
    return response.data;
  } else {
    console.error('ユーザーの取得に失敗しました:', response.error?.message);
    return null;
  }
}

移行のベストプラクティス

段階的な移行

  1. tsconfig.jsonの設定
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020", "DOM"],
    "allowJs": true,
    "checkJs": false,
    "strict": false,
    "noImplicitAny": false,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
  1. ファイルの段階的な変換
  • .js.ts に拡張子を変更
  • 型エラーを一つずつ修正
  • any 型から具体的な型に変更
  1. 厳密性の段階的な向上
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true
  }
}

よくある問題と解決策

// 問題:any型の乱用
function processData(data: any): any {
  return data.someProperty;
}

// 解決:適切な型定義


function processData(data: DataType): string {
  return data.someProperty;
}

// 問題:null/undefinedの処理
function getName(user: User): string {
  return user.name.toUpperCase(); // user.nameがnullの可能性
}

// 解決:null チェック
function getName(user: User): string {
  return user.name?.toUpperCase() ?? '名前なし';
}

まとめ

TypeScriptを導入することで:

  • コンパイル時エラー検出: バグを早期に発見
  • 優れたIDE支援: 自動補完とリファクタリング
  • コードの自己文書化: 型が仕様書の役割
  • チーム開発の効率化: 型による契約の明確化

段階的に導入し、チーム全体でTypeScriptの恩恵を享受しましょう!

頑張って! 🚀

Related Posts