Michi's Tech Blog

一人前のWebエンジニアを目指して

Zodに入門してみる

こんにちは!
スマレジ テックファームのMichiです!

今回の記事では、ZodというTypeScriptのライブラリに入門してみます。

Zodとは?

公式ドキュメントの説明が一番わかりやすかったので、和訳したものをそのまま転載します。

ZodはTypeScriptでスキーマ定義・バリデーション設定を行うライブラリです。 ここでいう「スキーマ」という用語は、単純な文字列から複雑なネストされたオブジェクトまで、あらゆるデータ型を広く指します。

Zodは可能な限り開発者フレンドリーで設計されています。ゴールは型宣言の重複をなくすことです。Zodではバリデーターを一度宣言すれば、Zodが自動的に静的なTypeScriptの型を推論してくれます。単純な型を複雑なデータ構造にまとめるのも簡単です。

使ってみる

スキーマとバリデーションの設計

会員情報のスキーマを設計するとします。Zodで定義する場合は次のようになります。

import { z } from "zod";

const userSchema = z.object({
  name: z.string().nonempty("アカウント名は必須です。"),
  email: z.string().email("有効なメールアドレスを入力してください。"),
  age: z
    .number()
    .int("整数で入力してください。")
    .nonnegative("0以上の値で入力してください。"),
  password: z
    .string()
    .regex(
      /^(?=.*[A-Z])(?=.*[a-z])(?=.*d)[A-Za-z0-9]{8,}$/,
      "パスワードは8文字以上で、英大文字・英小文字・数字を最低1回は使用してください。"
    )
});

各プロパティのデータ構造は以下の通りです。

  • name: 文字列。空入力を許可しない。
  • email: 文字列。メールアドレスの形式に沿ったデータのみ許容。
  • age: 数字。0以上の整数のみ許容。
  • passwod: パスワード。8文字以上で、英大文字・英子文字・数字の使用が最低1回は必要。

Zodの便利なところは、nonemptyemailのようにあらかじめ必要なバリデーション関数が用意されている点です。regex関数を使えば正規表現を使ってオリジナルのバリデーションを構築することもできます。

また、これらの引数にはエラーメッセージを登録することができます。これについては、また後ほど解説します。

TypeScriptの型生成

Zodで設計したスキーマからTyepScriptの型を逆生成できます。

type User = z.infer<typeof userSchema>;

User型の中身

これで、バリデーションと型を別々に定義する手間が省けます。

React Hook Formとの連携

Reactと連携して、画面側のバリデーションを作成します。

今回はZodとの相性が良いReact Hook Formを利用します。React Hook Formについての詳しい説明は本稿では省略するので、気になる方は公式ドキュメントやほかの解説記事をお読みください。

import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import "./App.css";

// スキーマの作成
const userSchema = z.object({
  name: z.string().nonempty("アカウント名は必須です。"),
  email: z.string().email("有効なメールアドレスを入力してください。"),
  age: z
    .number()
    .int("整数で入力してください。")
    .nonnegative("0以上の値で入力してください。"),
  password: z
    .string()
    .regex(
      /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)[A-Za-z0-9]{8,}$/,
      "パスワードは8文字以上で、英大文字・英小文字・数字を最低1回は使用してください。"
    ),
});

// スキーマからTypeScriptの型を逆生成
type User = z.infer<typeof userSchema>;

// コンポーネント
const SingUpForm = () => {
  // React Hook Formの利用
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<User>({
    resolver: zodResolver(userSchema),
  });

  const onSubmit = (data: User) => {
    // フォームデータを送信する処理(省略)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>アカウント名</label>
        <input {...register("name")} />
        {errors.name && <p>{errors.name.message}</p>}
      </div>
      <div>
        <label>Email</label>
        <input {...register("email")} />
        {errors.email && <p>{errors.email.message}</p>}
      </div>
      <div>
        <label>年齢</label>
        {/* フォームにバインドされる値は常に String型として扱われるため、
            React Hook Form側でNumber型に変換する */}
        <input {...register("age", { valueAsNumber: true })} />
        {errors.age && <p>{errors.age.message}</p>}
      </div>
      <div>
        <label>パスワード</label>
        <input type="password" {...register("password")} />
        {errors.password && <p>{errors.password.message}</p>}
      </div>
      <button type="submit">登録</button>
    </form>
  );
};

export default SingUpForm;

useFormの引数のresolverプロパティには、React Hook Formと連携しているバリデーションライブラリを設定できます。

今回はzodResolver設定し、引数には最初に定義したuserSchemaを渡します。

不正な値を入力しようとすると、React Hook Formが自動でZodと連携し、エラーメッセージを返してくれます。

まとめ

Zodについて紹介しました。

今回はフロントエンドだけでしたが、バックエンドもTypeScriptを採用している場合は、Zodで作成したスキーマと型をフロント/バック両方で使いまわせるので、とても便利ですよ。