メインコンテンツまでスキップ

第259章:parsesafeParse の使い分け

今回は 「バリデーション失敗したとき、例外で止める?それとも結果として受け取る?」 の話だよ〜😊 Valibot には大きく 2つの流儀 があるんだ👇 (Valibot)

  • parse:OKなら型つきデータを返す、NGなら ValiError を throw 💥 (Valibot)
  • safeParse:OK/NGを 結果オブジェクト で返す(.success / .output / .issues)📦 (Valibot)

まずは全体像を図でつかもう🗺️

Valibot Parse vs SafeParse


使い分けの結論(迷ったらこれ!)🎯

目的おすすめ理由
フォーム入力で「エラー文を表示したい」😊safeParse例外を出さずに .issues が取れる (Valibot)
“ここは壊れてたら止めたい” 設定ファイル/サーバー内部のデータ 💪parseNGなら即 throw(fail-fast)できる (Valibot)
try/catch が増えるのがイヤ🙃safeParseif 文でスッキリ書ける (Valibot)

サンプル:同じスキーマで両方やってみる🧪✨

1) スキーマを作る(src/validation/profile.ts みたいな場所)📁

import * as v from "valibot";

export const ProfileSchema = v.object({
name: v.pipe(
v.string("名前は必須だよ😊"),
v.minLength(2, "名前は2文字以上でお願い🙏")
),
email: v.pipe(
v.string("メールは必須だよ📧"),
v.email("メール形式じゃないかも…🤔")
),
});

export type Profile = v.InferOutput<typeof ProfileSchema>;

parsesafeParse も、通ったときの出力型(InferOutput がそのまま使えるよ🧠✨ (Valibot)


2) parse:成功は気持ちいいけど、失敗は throw 💥

import * as v from "valibot";
import { ProfileSchema } from "./validation/profile";

export function validateWithParse(input: unknown) {
try {
const data = v.parse(ProfileSchema, input);
// data は型つき(Profile)
return { ok: true as const, data };
} catch (err) {
// 失敗すると ValiError が飛んでくる
if (err instanceof v.ValiError) {
return { ok: false as const, issues: err.issues };
}
throw err; // それ以外は想定外なので投げ直し
}
}

parse「失敗=例外」 っていう強いスタイルだよ🔥 (Valibot)


3) safeParse:フォーム向き!結果で受け取る📦💕

import * as v from "valibot";
import { ProfileSchema } from "./validation/profile";

export function validateWithSafeParse(input: unknown) {
const result = v.safeParse(ProfileSchema, input);

if (result.success) {
// result.output は型つき(Profile)
return { ok: true as const, data: result.output };
}

// result.issues に全部入ってる
return { ok: false as const, issues: result.issues };
}

safeParse.successtrue/false で分かれて、OKなら .output、NGなら .issues が見れるよ〜✨ (Valibot)


React実戦:safeParse でエラー表示までやっちゃう😆🧡

ポイントはここ👇

  • safeParseissues を取る
  • そのままだと情報が多いので、v.flatten() で「表示しやすい形」にする (Valibot)
import { useState } from "react";
import * as v from "valibot";
import { ProfileSchema, type Profile } from "./validation/profile";

type FlatErrors = Record<string, string[]>;

export function ProfileForm() {
const [form, setForm] = useState({ name: "", email: "" });
const [errors, setErrors] = useState<FlatErrors>({});
const [saved, setSaved] = useState<Profile | null>(null);

const onSubmit = (e: React.FormEvent) => {
e.preventDefault();

const result = v.safeParse(ProfileSchema, form);

if (result.success) {
setErrors({});
setSaved(result.output);
return;
}

const flat = v.flatten<typeof ProfileSchema>(result.issues);
setErrors(flat.nested ?? {});
setSaved(null);
};

return (
<div style={{ maxWidth: 420 }}>
<h2>プロフィール登録 ✨</h2>

<form onSubmit={onSubmit}>
<div style={{ marginBottom: 12 }}>
<label>
名前:
<input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
/>
</label>

{errors["name"]?.map((m, i) => (
<p key={i} style={{ margin: "4px 0" }}>
{m}
</p>
))}
</div>

<div style={{ marginBottom: 12 }}>
<label>
メール:
<input
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
/>
</label>

{errors["email"]?.map((m, i) => (
<p key={i} style={{ margin: "4px 0" }}>
{m}
</p>
))}
</div>

<button type="submit">保存する 💾</button>
</form>

{saved && (
<pre style={{ marginTop: 16 }}>
🎉 保存できた!{"\n"}
{JSON.stringify(saved, null, 2)}
</pre>
)}
</div>
);
}

v.flatten() は、issues を { nested: { "email": ["..."] } } みたいに扱いやすく してくれるイメージだよ🧁 (Valibot)


ちょいテク:エラーを「最初の1個だけ」にしたい時🙋‍♀️

「全部のエラー出すと、ちょっと怖い😵」ってときは、設定で止められるよ! abortEarlyabortPipeEarly が用意されてる(parse の例が公式にあるよ)🧯 (Valibot)


まとめ🌸

  • parse:失敗で throw(try/catch 必須になりがち)💥 (Valibot)
  • safeParse:結果で受ける(フォーム&画面表示に超向く)📦 (Valibot)
  • エラー表示は issuesflatten が便利✨ (Valibot)

ミニチェック(1分)⏱️💡

  1. フォームでエラー文を出したい → どっち?
  2. 「ここ壊れてたら即落としてOK」な設定読み込み → どっち?
  3. safeParse の結果オブジェクトで、成功判定は何を見る?

(答え:1=safeParse、2=parse、3=result.success 😊)