React Hook FormとZod

## はじめに
React学習のアウトプットで、React Hook FormとZodについてまとめる。

## React Hook Formとは

### 概要
Reactのための軽量で高パフォーマンスなフォーム管理ライブラリ。
フォームの状態管理やバリデーションを簡潔に実装できる。

### 特徴
1. フォーム状態の自動管理
    - 入力値、エラー状態、バリデーションの状態を内部で効率的に管理
    - 開発者が状態を手動で設定する必要がない 
2. パフォーマンス
    - 入力フィールドの状態管理が個別に行われるため、全体の再レンダリングを最小限に抑える
    - 特に、大規模なフォームでパフォーマンスの違いが顕著
3. 柔軟なバリデーション
    - React Hook Form自体でシンプルなバリデーション（例: 必須チェック）をサポート
    - 外部バリデーションライブラリ（e.g., Zod, Yup）と統合することで、高度なバリデーションも可能
4. 軽量
    - サイズが小さく、依存関係が少ないため、パフォーマンスへの影響が少ない

## Zodとは

### 概要
スキーマ定義とバリデーションを行うためのTypeScript対応ライブラリ。
スキーマをもとに入力データを検証し、型安全なプログラミングを可能にする。

### 特徴
1. スキーマ定義とバリデーション
    - Zodを使って、データの構造（スキーマ）とルールを定義可能
        - 例: ユーザー名は文字列、メールは有効な形式、など
2. TypeScriptと統合
    - スキーマから型を自動生成できるため、型定義の重複を防止
    - フォームデータの型安全性を高める
3. 柔軟で強力なバリデーション
    - 入力値に対して簡単な条件（必須、最大文字数など）から複雑なルールまで適用可能
4. 簡単なAPI
    - シンプルな構文でスキーマを作成でき、直感的に使える

## React Hook Form × Zodの連携

React Hook Formのフォーム管理機能とZodの強力なバリデーション機能を組み合わせることで、型安全で高パフォーマンスなフォームを実現することができる

### 連携の流れ
1. スキーマ定義
    - Zodで入力フィールドのバリデーションルールを定義
2. Zodを`resolver`として設定
    - React Hook Formの`useForm`で`zodResolver`を指定し、Zodのバリデーションを統合
3. フォームの送信時にバリデーションを実行
    - Zodが定義されたスキーマを使い、入力値を検証
    - エラー時はReact Hook Formがエラー状態を管理

### 連携のメリット
1. 型安全性
    - フォームデータの型定義とバリデーションルールが同期
2. 簡潔なコード
    - フォーム管理とバリデーションを分離しつつ統合的に運用
3. 拡張性
    - 大規模プロジェクトでも柔軟に対応可能

## 実際に使ってみる

### 今回使用する技術
- React 18
- TypeScript
- Vite
- Zod
- @hookform/resolvers

### プロジェクトのセットアップ

#### 1-1. Viteプロジェクトの作成
```
npm create vite@latest my-form-app --template react-ts
cd my-react18-app
```

#### 1-2. React 18をインストール
```
npm install react@18 react-dom@18
```

#### 1-3. 必要な依存関係をインストール
```
npm install react-hook-form @hookform/resolvers zod
```

### フォームを実装
今回は3つの入力フォームを用意する。
```typescript
import React from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";

// Zodスキーマを定義
const schema = z.object({
  username: z.string().min(3, { message: "ユーザー名は3文字以上で入力してください" }),
  email: z.string().email({ message: "有効なメールアドレスを入力してください" }),
  password: z.string().min(6, { message: "パスワードは6文字以上で入力してください" }),
});

// 型を自動生成
type FormData = z.infer<typeof schema>;

function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data: FormData) => {
    console.log("送信されたデータ:", data);
  };

  return (
    <div style={{ maxWidth: "400px", margin: "0 auto", fontFamily: "Arial, sans-serif" }}>
      <h1>フォーム入力</h1>
      <form onSubmit={handleSubmit(onSubmit)}>
        {/* ユーザー名 */}
        <div>
          <label>ユーザー名:</label>
          <input {...register("username")} />
          {errors.username && <p style={{ color: "red" }}>{errors.username.message}</p>}
        </div>

        {/* メールアドレス */}
        <div>
          <label>メールアドレス:</label>
          <input type="email" {...register("email")} />
          {errors.email && <p style={{ color: "red" }}>{errors.email.message}</p>}
        </div>

        {/* パスワード */}
        <div>
          <label>パスワード:</label>
          <input type="password" {...register("password")} />
          {errors.password && <p style={{ color: "red" }}>{errors.password.message}</p>}
        </div>

        <button type="submit" style={{ marginTop: "10px" }}>送信</button>
      </form>
    </div>
  );
}

export default App;
```

### 開発サーバーを起動
以下のコマンドを実行してアプリケーションを起動。
```
npm run dev
```

### 動作確認
1. 正常な入力
    - フィールドに正しい値を入力し、「送信」ボタンを押すと、コンソールにフォームデータが出力される
```json
{
  "username": "testuser",
  "email": "test@example.com",
  "password": "password123"
}
```
2. 不正な入力
    - バリデーションエラーが発生すると、各フィールドの下にエラーメッセージが表示される
## Tips：リアルタイムでバリデーションを行うには
`useForm`の`mode`オプションを変更する。
```typescript
const {
  register,
  handleSubmit,
  formState: { errors },
} = useForm<FormData>({
  resolver: zodResolver(schema),
  mode: "onChange", // 入力中にバリデーションを行う
});
```