🤔 こんな問題に悩んでいませんか?
<input type="number" id="count" />
👆 このフォーム要素、見た目は数値入力用なのに、JavaScript側では、
const value = document.getElementById('count').value;
console.log(typeof value); // "string" 😱
文字列として扱われてしまいます!
📝 よくある解決パターン
こんな解決パターンを見かけることがあります。
// フォーム用とサーバー用、2つの型定義を作成
interface FormData {
count: string; // フォームからの入力は文字列
}
interface ProcessedData {
count: number; // 処理時は数値
}
このパターンだと、コードが冗長になり、型の不一致によるバグも発生しやすい問題があります。
✅ Zodのcoerce機能で解決!
Zodのcoerce.number()
を使えば、この問題を解決できます。
import { z } from "zod";
// たった1つの型定義でOK! 🎉
const formSchema = z.object({
count: z.coerce.number().int().min(1)
});
type FormData = z.infer<typeof formSchema>;
🚀 React Hook Formでの実装例
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
function CounterForm() {
const { register, handleSubmit } = useForm<FormData>({
resolver: zodResolver(formSchema)
});
const onSubmit = (data: FormData) => {
console.log(typeof data.count); // "number" 🎯
// 数値として直接計算できる!
const doubled = data.count * 2;
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="number" {...register("count")} />
<button type="submit">送信</button>
</form>
);
}
⚡ Next.jsのServer Actionsでも
'use server'
// Server Actionsでも同じスキーマが使える! 🔄
export async function saveCount(formData: FormData) {
const data = formSchema.parse({
count: formData.get('count')
});
// data.countは既に数値型 🧮
// データベースに保存する処理...
}
💡 coerce.number()のポイント
- 空文字列 →
0
に変換 - 文字列
"123"
→ 数値123
に変換 - バリデーションも同時に設定可能(
.int()
、.min()
など)
🌟 メリット
- 型定義の一元化: フォーム用とサーバー用で別々の型が不要に
- コードの簡素化: 変換処理を書く手間が省ける
- 型安全性の向上: フォーム入力からサーバー処理まで一貫した型で扱える
🎯 まとめ
Zodのcoerce.number()
を使えば、HTMLの<input type="number">
から取得した値が文字列型になる問題をサクッと解決できます。コードはシンプルに、型安全性は向上し、開発効率もアップします!