dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

Total TypeScriptのZodチュートリアルでZodに入門した

はじめに

Zod というバリデーションライブラリが非常に流行っているようなので、素振りした。

www.totaltypescript.com

このチュートリアルはたしか Twitter で流れてきて知ったのだが
今見ると Zod の公式ドキュメントからも Resources として紹介されているので、そこそこ信頼していいコンテンツなのだと判断した。

チュートリアルについて

チュートリアルと名がついているが、内容は全 10 問のエクササイズを解くという構成。

あらかじめ型チェックのエラーまたはランタイムのエラーが発生するサンプルコードが問題として用意されており、そのコードを修正しながら Zod の基本的な使い方を学ぶ。 チュートリアルには Zod の使い方の説明は特にないので、チュートリアルの問題を解くために Zod の公式ドキュメントを読んで必要な箇所を理解する、という感じ。

Rust でいう rustlings に似てるなと思った。

Web エディタでも解けるし、 https://github.com/total-typescript/zod-tutorial にもコードが公開されており、git clone して手元で動かしながら問題を解くこともできる。
また、全 10 問といったがリポジトリには隠しステージっぽく 11〜14 問目まである。

各問で学べるのは(自分の理解では)以下。

  1. Runtime Type Checking with Zod
    • 基本。プリミティブな値 (number) に対するスキーマ定義および parse メソッドæ
  2. Verify Unknown APIs with an Object Schema
    • 基本。z.object({ … }) でオブジェクトのスキーマを定義する
    • parse 後はスキーマに定義したkeyのみが残る
  3. Create an Array of Custom Types
    • z.array による配列の定義方法
  4. Extract a Type from a Passer Object
    • z.infer<typeof スキーマ>スキーマから型の取り出し
  5. Make Schemas Optional
  6. Set a Default Value with Zod
  7. Be Specific with Allowed Types
  8. Complex Schema Validation
    • https://zod.dev/?id=strings
    • string は文字列長の min/max だけでなく、 emailurl の形式を強制する便利なメソッドがある
  9. Reduce Duplicated Code by Composing Schemas
    • .extend を利用したオブジェクトの拡張と、 .merge を使ったオブジェクトのマージ
  10. Transform Data from Within a Schema
    • .transform を使うとパース後のデータの変換ができる

終わった後に公式ドキュメントをざっと眺めて、たしかに主要なところはカバーしてるのかなと思った。

学び:Zod の基本

ここからはチュートリアルプラス公式ドキュメントも読んで学んだことをメモ。

Zod の特徴

Introduction に挙げられてるのは以下。

  • Zero dependencies
  • Node.js やすべてのモダンブラウザで動く
  • 小さい:minified + zipped で 8kb
  • イミュータブル
  • 簡潔でチェイン可能なインターフェース
  • 関数型アプローチ: parse, don’t validate (←これリンク先の記事まで見てないのでわからなかった)
  • プレーンなJSでも動く

基本的な使い方

const stringSchema = z.string() のようにプリミティブなスキーマも定義できるが、実際使うとなると多くがオブジェクト形式だと思うのでオブジェクトのスキーマ例を載せる。

import { z } from "zod";

const User = z.object({
  username: z.string(),
});

User.parse({ username: "Ludwig" });

// extract the inferred type
type User = z.infer<typeof User>;
// { username: string }

(コードは Basic usage より引用)

その他、 .shape でオブジェクトの特定のkeyのスキーマを取得できたり、

User.shape.name; // => string schema

.merge .pick .omit partial など TypeScript の型操作的な API は一通り提供されている。

.refine:カスタムバリデーション

独自のバリデーションロジックを実装したい場合は .refine メソッドを使う。

const myString = z.string().refine((val) => val.length <= 255, {
  message: "String can't be more than 255 characters",
});

(コードは公式ドキュメントより引用)

.transform:パースした値の変換

定義したスキーマに続けて .transform というメソッドを呼び出すと、パースした値を変換できる。

const StarWarsPerson = z
  .object({
    name: z.string(),
  })
  .transform((person) => ({
    name: person.name,
    nameAsArray: person.name.split(" "),
  }));


const person = {
  name: "Luke Skywalker",
};
const parsed = StarWarsPerson.parse(person);
console.log(parsed);
// { name: 'Luke Skywalker', nameAsArray: [ 'Luke', 'Skywalker' ] }

Yup との違いは?

現職ではフォームのバリデーションに Yup を使っているので、違いが気になる。
最初、スキーマ定義から型を推論できるのは Zod すごいなって思ったけどそれは Yup でもできるらしい(InferType)。知らなかった。。。

というわけでほとんど違いがわからずにいたが、公式ドキュメントの Comparison > Yup で比較されていた。何点か違いが列挙されているが、

  • Yup はオブジェクトのすべてのフィールドがデフォルトで optional(Zod は required が基本)
  • partial や deepPartial などのメソッドがない
  • promise スキーマがない
  • function スキーマがない
  • union や intersection スキーマがない

というわけで、スキーマの充実度では Zod に分があるよという主張のよう。

また、 Yup の方が型推論がいまいちだ、という比較記事も見かけた。
参考:Reactで使えるバリデーションライブラリを紹介! - bagelee(ベーグリー)

参考リンク