Viteで始める高速Web開発 | 第7章: TypeScriptとテストで強化

## はじめに
第6章でバックエンドと連携しました。今回は、TypeScriptでコードを安全にし、Vitestでテストを追加します。これでアプリの信頼性と保守性が向上します。

## 目標
- TypeScriptをViteに統合する  
- Vitestでユニットテストを実装する  
- ToDoアプリを強化する  

## TypeScriptの導入
### 1. TypeScriptプロジェクトへの移行
既存プロジェクトをTypeScriptに変換：
```bash
npm install typescript @types/react @types/react-dom --save-dev
```
- プロジェクト作成時に`--template react-ts`を選んだ場合は不要。

`tsconfig.json`がなければ、生成：
```bash
npx tsc --init
```
`vite.config.js`を`vite.config.ts`に変更：
```typescript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:5000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
});
```

### 2. App.tsxへの変換
`src/App.jsx`を`src/App.tsx`にリネームし、型を追加：
```tsx
import { useState, useEffect } from 'react';
import './App.css';

interface Todo {
  id: number;
  text: string;
}

function App() {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [input, setInput] = useState<string>('');
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    fetchTodos();
  }, []);

  const fetchTodos = async () => {
    try {
      const response = await fetch('/api/todos');
      const data: Todo[] = await response.json();
      setTodos(data);
    } catch (error) {
      console.error('エラー:', error);
    } finally {
      setLoading(false);
    }
  };

  const addTodo = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!input) return;

    try {
      const response = await fetch('/api/todos', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ text: input }),
      });
      const newTodo: Todo = await response.json();
      setTodos([...todos, newTodo]);
      setInput('');
    } catch (error) {
      console.error('追加エラー:', error);
    }
  };

  if (loading) return <div>読み込み中...</div>;

  return (
    <div className="App">
      <header>
        <h1>{import.meta.env.VITE_APP_TITLE}</h1>
      </header>
      <main>
        <form onSubmit={addTodo}>
          <input
            type="text"
            value={input}
            onChange={(e) => setInput(e.target.value)}
            placeholder="タスクを入力"
          />
          <button type="submit">追加</button>
        </form>
        <ul>
          {todos.map((todo) => (
            <li key={todo.id}>{todo.text}</li>
          ))}
        </ul>
      </main>
    </div>
  );
}

export default App;
```

## Vitestの導入
### 1. インストール
```bash
npm install vitest jsdom @testing-library/react @testing-library/jest-dom --save-dev
```

### 2. 設定
`vite.config.ts`にテスト設定を追加：
```typescript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:5000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: './src/test/setup.ts',
  },
});
```

`src/test/setup.ts`を作成：
```typescript
import '@testing-library/jest-dom';
```

`package.json`にスクリプトを追加：
```json
"scripts": {
  "test": "vitest"
}
```

### 3. テストの実装
`src/App.test.tsx`を作成：
```tsx
import { render, screen, fireEvent } from '@testing-library/react';
import App from './App';

describe('App', () => {
  it('タスクを追加できる', async () => {
    render(<App />);
    await screen.findByText('私のToDoアプリ'); // タイトルが表示されるまで待機

    const input = screen.getByPlaceholderText('タスクを入力');
    const button = screen.getByText('追加');

    fireEvent.change(input, { target: { value: 'テストタスク' } });
    fireEvent.click(button);

    expect(await screen.findByText('テストタスク')).toBeInTheDocument();
  });
});
```
- バックエンド依存のため、モックが必要な場合も考慮。

## 動作確認
- TypeScript: `npm run dev`でエラーがないか確認。
- テスト: `npm test`でテストを実行。

## 次回予告
第8章でシリーズを総括し、次の学習ステップを提案します。お楽しみに！

---

この記事が役に立ったら、ぜひ「いいね」や「ストック」をお願いします！質問や感想はコメント欄で気軽にどうぞ。次回も一緒に学びましょう！