【React修行日記】React Hook Form+Zodで型安全バリデーション
学習の目的
- Zodを使ったスキーマバリデーションの基本を理解する
- React Hook FormにZod Resolverを組み込む方法を習得する
- 実務でも使える型安全なフォーム作成の流れを体験する
Zodとは
ZodはTypeScript向けのスキーマバリデーションライブラリ。
JavaScriptの型チェックだけでは足りないフォームの入力値の検証や、サーバーに送る前のデータチェックを簡単に実装できるのが強み。
特徴:
-
型推論が強力
Zodでスキーマを定義すると、そこから自動でTypeScriptの型を生成できる。フォームで使うデータ型を別途定義する必要がなくなる。 -
バリデーションを簡潔に記述
文字列長、数値の範囲、正規表現チェックなどをチェーン式で書ける。独自ルールも.refine()で追加可能。 -
フォームとの連携が容易
React Hook Formなどのフォーム管理ライブラリと組み合わせると、バリデーションルールをコンポーネント側に書かずに済む。フォームの状態管理と型安全性をまとめて扱える。
React Hook Form × Zodの関係
React Hook Formはフォームの状態管理を効率化するライブラリで、入力値の追跡やエラー表示が簡単にできる。
ただし、単体ではバリデーションのルールを手動で書く必要があるため、フォームが大きくなるとコードが煩雑になりがち。
ここでZodを組み合わせると:
- 入力値が正しい型か自動チェックできる
- 正規表現や独自条件のバリデーションもスキーマ内にまとめられる
- TypeScriptの型とバリデーションが連動するので、フォーム側で型安全に扱える
つまり、React Hook Formが「フォームの状態を管理」し、Zodが「入力値が正しいかどうかを判定」する役割を担うイメージ。
両者を組み合わせることで、型安全かつバリデーションが一元管理されたフォームの実装が可能。
導入手順
今回は前回React Hook Formで作成した「ユーザー登録フォーム」のバリデーション部分を、Zodを使って簡潔かつ型安全に置き換えてみた...!
▽前回の記事
① ZodとResolverのインストール
まずはパッケージをプロジェクトにインストールする。
npm install zod @hookform/resolvers
これでZod本体と、React Hook FormにZodを統合するための公式resolverが使えるようになる。
② スキーマを定義する
React Hook Formで使うフォームのバリデーションを、Zodスキーマとしてまとめて定義する。
import { z } from "zod";
export const userSchema = z.object({
name: z
.string()
.min(2, { message: "2文字以上入力してください" })
.max(20, { message: "20文字以内で入力してください" }),
email: z.email({ message: "メールアドレスの形式が正しくありません" }),
age: z
.string()
.optional()
.refine((val) => !val || /^[0-9]+$/.test(val), {
message: "半角数字で入力してください",
}),
});
export type FormData = z.infer<typeof userSchema>;
オブジェクトのスキーマ化
export const userSchema = z.object({
...
});
フォームのように複数の入力項目がある場合、まずZodではz.object()を使ってオブジェクトのスキーマを作成する。
オブジェクトのキーがフォームの項目名に対応し、値の型やバリデーションルールをまとめて定義する。
例えば、今回のユーザー登録フォームなら、名前やメールアドレス、年齢などの各フィールドを z.object({ ... })の中に記述していく。
基本の型指定
次に各フィールドの基本的な型を指定する。
Zodでは文字列ならz.string()、数値なら z.number()、真偽値ならz.boolean()で型を宣言する。
この型指定だけでも、入力値が想定した型であるかどうかのチェックが可能。
バリデーションルールの追加
さらに型に加えてバリデーションルールを追加できる。
文字列の長さの制限や数値の範囲チェックは .min()や.max()で設定可能。
正規表現や独自条件で検証したい場合は .refine()を使用。
例えば年齢フィールドを任意入力にして、半角数字以外が入力された場合にエラーを出す、といった柔軟なルールも .refine()で実装できる。
-
email()- 文字列がメール形式かチェック
-
z.email({ message: "〜" })のようにメッセージをカスタマイズ可能
-
optional()- 空値を許容する(必須項目ではない)
- 入力がなくてもバリデーションエラーにならない
型を自動生成
export type FormData = z.infer<typeof userSchema>;
最後に、作成したスキーマからTypeScriptの型を自動生成することができる。
z.infer<typeof userSchema>を使えば、スキーマに基づいた型FormDataを生成でき、React Hook Form などのフォームライブラリで型安全に利用できる。
これでバリデーションと型安全性の両方を一度に実現できるのがZodの強み。
③フォームにZod Resolverを組み込む
React Hook Formを利用して作成したuseForm()に、resolverとしてZodを渡す。
import { userSchema, type FormData } from "@/schemas/userSchema"; //追加
const {
register,
handleSubmit,
formState: { errors, isValid },
} = useForm<FormData>({
mode: "onChange",
resolver: zodResolver(userSchema), //追加
});
これでregister()にバリデーションルールを書く必要がなくなり、フォーム全体のルールがZodスキーマで一元管理可能となる。
ポイント
-
resolver: zodResolver(userSchema)でZod スキーマをReact Hook Formに組み込む - 型定義
FormDataを指定することで、フォームデータの型安全性も確保
④ フォーム部分はほぼそのまま使える
既存のフォームに大きな変更は不要で、registerは同じように使えるので、バリデーション部分のみを削除する。
▼Before
{errors.name && (
<p className="text-left text-sm text-red-500">
{errors.name.message}
</p>
)}
<Input
type="text"
id="name"
placeholder="例:サンプル太郎"
{...register("name", {
required: "名前は必須です",
minLength: { value: 2, message: "2文字以上入力してください" },
maxLength: { value: 20, message: "20文字以内で入力してください" },
})}
/>
▼After
{errors.name && (
<p className="text-left text-sm text-red-500">
{errors.name.message}
</p>
)}
<Input
type="text"
id="name"
placeholder="例:サンプル太郎"
{...register("name")}
/>
Zodスキーマで設定したバリデーションに沿って、errorsに自動で反映される。
まとめ
- Zodを使うことで、フォームの型安全性とバリデーションの管理を一元化できる。
- React Hook Formと組み合わせることで、実務でも使える効率的なフォームが作れる。
参考
Discussion