以下の Zod スキーマから TypeScript の型を導出したい。正しい書き方はどれか。 const UserSchema = z.object({ name: z.string(), age: z.number().optional(), });
解説
z.infer は TypeScript の型レベルのユーティリティであり、関数呼び出しではありません。z.infer<typeof UserSchema> のように型引数としてスキーマの型を渡す必要があります。選択肢Aの z.infer(UserSchema) は関数呼び出しの構文であり、z.infer はランタイムに呼び出せる関数ではないためコンパイルエラーになります。選択肢Cは z.Infer と大文字始まりにしていますが、正しくは小文字の z.infer です。選択肢Dの UserSchema.infer() もスキーマインスタンスにそのようなメソッドは存在しません。なぜ typeof が必要なのかここが最も混乱しやすいポイントです。UserSchema は実行時に存在する値(変数)です。一方 z.infer<...> のジェネリクスが受け取るのは型です。TypeScript では値と型は別の名前空間に属するため、値から型を取り出すには typeof 演算子が必要になります:// UserSchema は「値」 const UserSchema = z.object({ name: z.string() }); // typeof UserSchema で「型」を取得し、z.infer に渡す type User = z.infer<typeof UserSchema>; // => { name: string }この typeof は JavaScript のランタイム typeof(typeof x === 'string')とは別物で、TypeScript の型レベルでのみ動作する演算子です。スキーマと型を二重定義しないための設計パターンZod を使わない場合、バリデーションロジックと TypeScript の型定義を別々に書くことになり、両者が乖離するリスクがあります。z.infer を使えばスキーマを Single Source of Truth(信頼できる唯一の情報源)として型を自動導出できるため、定義の重複と不整合を防げます。実務では以下のようにスキーマと型をセットで export するのが一般的です:// schemas/user.ts export const UserSchema = z.object({ name: z.string(), age: z.number().optional(), }); export type User = z.infer<typeof UserSchema>;.optional() と z.infer の関係スキーマで .optional() を付けたフィールドは、z.infer で導出した型でもそのまま省略可能なプロパティになります。上記の例では age は number | undefined として推論されます。同様に .nullable() なら number | null、.default(0) なら入力時は省略可能ですが出力型では number として確定します。この入力と出力で型が変わるケースでは z.input<typeof Schema> と z.output<typeof Schema> を使い分けることもできます。