Mundarija (35)
- Bu darsda
- 1. Nega bu kerak?
- 2. Formadan nima keladi?
- 2.1 Hamma narsa — satr
- 2.2 z.coerce — majburan aylantirish
- 2.3 Bo'sh maydonni undefined ga aylantirish
- 3. Ixtiyoriy maydonlar
- 3.1 optional, nullable, default
- 3.2 Checkbox: "Shartlarga roziman"
- 4. transform — qiymatni o'zgartirish
- 4.1 Telefon: tekshir, keyin raqamlarga aylantir
- 4.2 Teglar: satrdan massivga
- 4.3 Ikki tur: z.input va z.output
- 5. refine va superRefine
- 5.1 Ikki maydonni solishtirish
- 5.2 Tuzoq: tur xatosi bo'lsa, refine ishlamaydi
- 5.3 superRefine — bir nechta xato
- 6. O'zbekcha xabarlar
- 6.1 Uch daraja
- 6.2 Tayyor tarjima: z.locales.uz()
- 7. zod/mini — vazifalar dagi ko'rinish
- 7.1 Bir xil qoidalar, boshqa yozuv
- 8. Vazifalar qadami: tahrir formasining sxemasi (holat 32)
- 8.1 sxemalar/vazifa-formasi.ts
- 8.2 Test: tekshiruv/vazifa-formasi.test.js
- 8.3 Tekshiruv
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): natijani ayting
- 2-mashq (o'rta): bron sxemasi
- 3-mashq (qiyin): buyurtma — bir nechta xato
- 4-mashq: Vazifalar qadami
- 11. Real ishda
- Xulosa
- Manbalar
Zod formalar uchun: coerce, refine, superRefine va transform
Qisqacha: Formadan keladigan har qiymat — satr, bo'sh maydon —
""(undefinedemas). Zod 4 daz.coerce.number()satrni songa aylantiradi, lekin""ni0qiladi — buniz.preprocessbilan to'sing. Bir nechta maydonni birga tekshirish —.refine(fn, { path }), bir nechta xato —.superRefine..transformqiymatni o'zgartiradi, shuning uchun sxemaning kirish va chiqish turi farq qiladi (z.input/z.output). Xabarlarni har qoidaning o'zida o'zbekcha yozing.
Bu darsda
- Formadan keladigan qiymatlar Zod'ga qanday tushishini va
coercening""→0tuzog'ini bilasiz. - Ixtiyoriy maydonni (
optional,nullable,default) bo'sh satr bilan to'g'ri ishlatasiz. refinebilan ikki maydonni solishtirasiz, xatoni kerakli maydonga (path) qo'yasiz;superRefinebilan bir nechta xato chiqarasiz.transformqiymatni o'zgartirgandaz.inputvaz.outputfarqini tushunasiz.- Xabarlarni o'zbekchalaysiz va
vazifalartahrir formasining sxemasini (zod/mini) o'qiysiz.
Oldin bilishingiz kerak: Zod va turni sxemadan chiqarish, API qatlami va type-safe kontrakt, React Hook Form I.
1. Nega bu kerak?
Oldingi darsda bron formasining qoidalarini register ichida yozdik: required, min, pattern. Ishladi. Lekin Jasur aka uch narsani so'radi:
- "Ketish vaqti kelishdan keyin bo'lsin." Bu — ikki maydonni birga tekshirish.
registerqoidasi faqat o'z maydonini ko'radi. - "Telefon serverga faqat raqamlar bo'lib borsin:
998900000000." Mehmon esa+998 90 000 00 00deb yozadi. Qiymatni tekshirish bilan birga o'zgartirish kerak. - "Server ham xuddi shu qoidalarni tekshirsin." JSX ichidagi qoidalarni serverga olib bo'lmaydi.
Bularning uchalasini bitta joy hal qiladi — sxema. Zod'ni 15-qismda o'rgangansiz: z.object, z.infer, parse va safeParse. API qatlami darsida esa vazifalar server javobini zod/mini bilan tekshira boshladi. Bugun Zod'ni formalar tomonidan ko'ramiz. Formaning o'ziga xos muammolari bor: hamma narsa satr, bo'sh maydon "", bitta xato bir nechta maydonga tegishli.
Hayotiy o'xshatish: bojxona. Yo'lovchi (forma qiymati) chegaradan o'tishi uchun uch narsa bo'ladi. Hujjat tekshiriladi (tur va qoida). Pul so'mga almashtiriladi (transform). Ba'zan ikki hujjat bir-biriga solishtiriladi: chipta va pasport bir odamnikimi (refine). Sxema — shu bojxonaning qoidalar kitobi, va u chegaraning ikkala tomonida — brauzerda ham, serverda ham — bir xil.
Maslahat: Bu darsdagi Zod misollari saytda «Ishga tushir» bilan ishlaydi (natija oynasida
zod4.6.5 bor). Natija oynasi xabarlarni faqat inglizcha beradi — o'zbekcha matnni sxemada o'zimiz yozamiz. Konsol ko'rinishi — natija panelidagi kabi.
2. Formadan nima keladi?
2.1 Hamma narsa — satr
Brauzer maydon qiymatini doim satr qilib beradi. <input type="number"> da ham. RHF valueAsNumber siz, FormData esa doim shunday (Formalar II):
| Maydon | Mehmon nima qildi | Qiymat |
|---|---|---|
type="text" |
hech narsa yozmadi | "" |
type="number" |
4 yozdi |
"4" |
type="date" |
sanani tanladi | "2026-10-14" |
<select> |
"Manti" ni tanladi | "manti" |
| checkbox (RHF) | belgiladi | true |
checkbox (FormData) |
belgiladi / belgilamadi | "on" / kalit yo'q |
z.number() satrni qabul qilmaydi:
import { z } from "zod";
const Guests = z.number().int().min(1);
const result = Guests.safeParse("4");
console.log(result.success);
console.log(result.error.issues[0].message);Konsolda:
false
Invalid input: expected number, received stringTarjimasi: "Noto'g'ri kirish: son kutilgan edi, satr keldi". Zod haq — "4" haqiqatan ham satr.
2.2 z.coerce — majburan aylantirish
z.coerce.number() (coerce — "majburlash") tekshirishdan oldin qiymatni Number(…) dan o'tkazadi. Satr songa aylanadi, keyin odatdagi qoidalar ishlaydi:
import { z } from "zod";
const Num = z.coerce.number();
console.log(Num.parse("4"), Num.parse(" 12 "), Num.parse("1e3"));
console.log(Num.parse(""));
const bad = Num.safeParse("to'rt");
console.log(bad.error.issues[0].message);Konsolda:
4 12 1000
0
Invalid input: expected number, received NaNUchta kutilmagan narsa bor. " 12 " — bo'shliqlar jim kesildi. "1e3" — ilmiy yozuv, 1000. Eng xavflisi — ikkinchi qator: bo'sh satr 0 ga aylandi. Number("") — 0 (turlarni aylantirish). Mehmon "Mehmonlar soni" ni bo'sh qoldirdi, sxema esa "0 kishi" deb qabul qildi. .min(1) bo'lsa, xabar "Too small: expected number to be >=1" bo'ladi — "maydonni to'ldiring" emas.
Zod 4 da coerce lar: z.coerce.number(), z.coerce.string(), z.coerce.boolean(), z.coerce.bigint(), z.coerce.date(). z.coerce.boolean() dan ehtiyot bo'ling: u Boolean(…) ni ishlatadi, "false" satri ham true bo'ladi (bo'sh bo'lmagan satr).
2.3 Bo'sh maydonni undefined ga aylantirish
Forma uchun to'g'ri yo'l: avval "" ni "qiymat yo'q" (undefined) ga aylantiramiz, keyin son qilamiz. Buni z.preprocess qiladi — "tekshiruvdan oldin ishlov":
import { z } from "zod";
const emptyToUndefined = (v) => (v === "" ? undefined : v);
const Guests = z.preprocess(
emptyToUndefined,
z.coerce
.number({ error: "Nechta mehmon?" })
.int("Butun son yozing")
.min(1, "Kamida 1 kishi")
.max(12, "Ko'pi bilan 12 kishi"),
);
for (const input of ["", "4", "2.5", "15"]) {
const r = Guests.safeParse(input);
const shown = r.success ? r.data : r.error.issues[0].message;
console.log(input, "→", shown);
}Konsolda:
→ Nechta mehmon?
4 → 4
2.5 → Butun son yozing
15 → Ko'pi bilan 12 kishiBirinchi qatorda input bo'sh satr, shuning uchun qator → bilan boshlanadi. { error: "Nechta mehmon?" } — tur xatosi uchun xabar: qiymat yo'q (undefined) yoki son emas. Qolgan har qoidaga ham o'z xabari berildi.
Tekshirib ko'ring:
z.preprocesssiz, faqatz.coerce.number().min(1, "Kamida 1 kishi")bo'lsa, bo'sh maydon qanday xabar beradi?
Javob
"Kamida 1 kishi". "" → 0 bo'ladi, 0 < 1 — min xatosi. Xabar texnik jihatdan to'g'ri, lekin mehmon uchun chalkash: u "0 kishi" deb yozmagan, hech narsa yozmagan. preprocess bo'lsa — "Nechta mehmon?", ya'ni "to'ldiring" ma'nosidagi xabar. Agar .min umuman bo'lmasa, coerce yolg'iz "" ni jimgina 0 qilib o'tkazib yuborardi — eng yomon holat.
3. Ixtiyoriy maydonlar
3.1 optional, nullable, default
Bron formasida email ixtiyoriy: yozsa — chek yuboramiz, yozmasa — yo'q. Zod va turni sxemadan chiqarish darsidagi qurilish bloklarini eslaymiz:
| Zod | Qabul qiladi | Chiqish turi |
|---|---|---|
S.optional() |
undefined (yoki kalit yo'q) |
T | undefined |
S.nullable() |
null |
T | null |
S.nullish() |
undefined va null |
T | null | undefined |
S.default(x) |
undefined → x bilan almashtiriladi |
T |
Muammo: forma undefined emas, "" yuboradi. z.email().optional() esa "" ni email deb tekshiradi:
import { z } from "zod";
const Email = z.email("Email noto'g'ri").optional();
console.log(Email.safeParse(undefined).success);
console.log(Email.safeParse("").error.issues[0].message);
const EmailOrEmpty = z.union([
z.literal(""),
z.email("Email noto'g'ri"),
]);
console.log(EmailOrEmpty.safeParse("").success);
console.log(EmailOrEmpty.safeParse("ali@example.com").success);
console.log(EmailOrEmpty.safeParse("ali@").error.issues[0].message);Konsolda:
true
Email noto'g'ri
true
true
Email noto'g'riz.email() — Zod 4 dagi yangi yozuv. Eski kodda z.string().email() ni ko'rasiz — u hali ishlaydi, lekin "eskirgan" (deprecated) deb belgilangan. z.url(), z.uuid(), z.iso.date() ham shunday — yuqori darajadagi funksiyalar.
Ikki yechim bor. z.union([z.literal(""), …]) — "yo bo'sh, yo to'g'ri email". Natija "" bo'lib qoladi. Yoki «Bo'sh maydonni undefined ga aylantirish» bo'limidagi preprocess — natija undefined bo'ladi. Serverga "email yo'q" ni undefined (yoki null) qilib yuborish odatda tozaroq.
Diqqat:
.optional()formadagi bo'sh matn maydonini "ixtiyoriy" qilmaydi.z.string().optional()""ni qabul qiladi, chunki""— satr.z.string().min(1).optional()esa""ni rad etadi. "Bo'sh bo'lsa ham bo'ladi" degan qoidani""bilan sinab ko'ring.
3.2 Checkbox: "Shartlarga roziman"
Majburiy belgi uchun z.boolean() yetmaydi — false ham boolean. z.literal(true) — faqat true:
import { z } from "zod";
const Agree = z.literal(true, { error: "Qoidalarga rozilik kerak" });
console.log(Agree.safeParse(true).success);
console.log(Agree.safeParse(false).error.issues[0].message);Konsolda:
true
Qoidalarga rozilik kerak4. transform — qiymatni o'zgartirish
4.1 Telefon: tekshir, keyin raqamlarga aylantir
.transform(fn) — tekshiruvdan keyin qiymatni o'zgartiradi. Telefon mehmon uchun chiroyli ko'rinishda yoziladi, server esa raqamlarni oladi:
import { z } from "zod";
const PHONE = /^\+998 \d{2} \d{3} \d{2} \d{2}$/;
const Phone = z
.string()
.trim()
.regex(PHONE, "Namuna: +998 90 000 00 00")
.transform((s) => s.replace(/\D/g, ""));
console.log(Phone.parse(" +998 90 000 00 00 "));
console.log(Phone.safeParse("90 000 00 00").error.issues[0].message);Konsolda:
998900000000
Namuna: +998 90 000 00 00Tartib muhim: trim → regex → transform. Zanjir chapdan o'ngga ishlaydi. regex chiroyli ko'rinishni tekshiradi, transform esa undan keyin bo'shliq va + ni olib tashlaydi. \D — "raqam bo'lmagan har belgi" (muntazam ifodalar).
.trim() ham o'zgartiradi, lekin u turni o'zgartirmaydi: satr kirdi — satr chiqdi. Zod 4 da bunday qoidalar (trim, toLowerCase) "overwrite" — "ustidan yozish" deyiladi. .transform esa istalgan turga aylantira oladi.
4.2 Teglar: satrdan massivga
Mehmon taomlarni vergul bilan yozadi: osh, manti, ,lag'mon. Bizga massiv kerak:
import { z } from "zod";
const Dishes = z
.string()
.transform((s) => s.split(",").map((t) => t.trim()).filter(Boolean))
.refine((list) => list.length > 0, "Kamida bitta taom yozing");
console.log(Dishes.parse("osh, manti, ,lag'mon"));
console.log(Dishes.safeParse(" , ").error.issues[0].message);Konsolda:
(3) ['osh', 'manti', 'lag'mon']
Kamida bitta taom yozing.filter(Boolean) — bo'sh bo'laklarni tashlaydi (filter). .refine transform'dan keyin turibdi — u allaqachon massivni ko'radi. Qoida: transform'dan keyingi har tekshiruv o'zgargan qiymatni oladi.
4.3 Ikki tur: z.input va z.output
Endi Dishes ning ikki "yuzi" bor: kirishda satr, chiqishda massiv. TypeScript buni ajratadi (bu blok — TS tekshiruvi bilan):
import { z } from "zod";
const Dishes = z.string().transform((s) => s.split(","));
type DishesIn = z.input<typeof Dishes>;
type DishesOut = z.output<typeof Dishes>;
const typed: DishesIn = "osh, manti";
const parsed: DishesOut = "osh, manti";Xato:
TS2322: Type 'string' is not assignable to type 'string[]'.z.input — string (forma shuni beradi), z.output (= z.infer) — string[] (ilova shuni ishlatadi). typed qatori xatosiz, parsed esa xato: chiqish turi massiv. Bu farq kelgusi darsda muhim bo'ladi: forma kirish turi bilan to'ldiriladi, yuborish funksiyasi esa chiqish turini oladi.
z.input |
z.output |
|
|---|---|---|
| Kim ishlatadi | forma maydonlari, defaultValues |
onSubmit, server so'rovi |
Phone |
string ("+998 90 …") |
string ("998900000000") |
Dishes |
string |
string[] |
z.coerce.number() |
unknown |
number |
Tekshirib ko'ring:
z.string().transform((s) => s.length).refine((n) => n > 2)—refinenimani oladi vaz.outputqanday tur?
Javob
refine sonni oladi — transform natijasini (satr uzunligini). z.output — number, z.input — string. Zanjir chapdan o'ngga: avval satr tekshiriladi, keyin uzunlikka aylanadi, keyin uzunlik tekshiriladi.
5. refine va superRefine
5.1 Ikki maydonni solishtirish
Jasur akaning birinchi talabi: "ketish kelishdan keyin". .refine ni obyekt sxemasiga qo'yamiz — u butun obyektni oladi. path — xato qaysi maydon ostida chiqishi:
import { z } from "zod";
const Booking = z
.object({
name: z.string().trim().min(2, "Ism kamida 2 harf"),
from: z.string(),
to: z.string(),
})
.refine((b) => b.to > b.from, {
error: "Ketish kelishdan keyin bo'lsin",
path: ["to"],
});
const input = { name: "A", from: "19:00", to: "18:00" };
const r = Booking.safeParse(input);
console.log(r.error.issues.map((i) => `${i.path}: ${i.message}`));
console.log(z.flattenError(r.error).fieldErrors);Konsolda:
(2) ['name: Ism kamida 2 harf', "to: Ketish kelishdan keyin bo'lsin"]
{name: ['Ism kamida 2 harf'], to: ["Ketish kelishdan keyin bo'lsin"]}"18:00" > "19:00" — satrlarni solishtirish, "HH:MM" ko'rinishida u vaqt tartibiga mos (satrlarni solishtirish). path: ["to"] bo'lmasa, xato obyektning o'ziga tegishli bo'lardi (path bo'sh) va forma uni hech bir maydon ostiga qo'ya olmasdi.
z.flattenError — xatolarni forma uchun qulay ko'rinishga keltiradi: fieldErrors — maydon → xabarlar massivi, formErrors — hech qaysi maydonga tegishli bo'lmaganlari. Kelgusi darsdagi resolver ham xatolarni shu tamoyil bilan errors.to.message ga joylaydi.
error: — Zod 4 dagi nom. Eski kodda message: ni ko'rasiz — u ham ishlaydi, lekin yangi kodda error yozing.
5.2 Tuzoq: tur xatosi bo'lsa, refine ishlamaydi
Yuqoridagi misolda name xato bo'lsa ham refine ishladi — ikkala xabar chiqdi. Endi guests maydonini qo'shamiz va uni yubormaymiz:
import { z } from "zod";
const Booking = z
.object({ from: z.string(), to: z.string(), guests: z.number() })
.refine((b) => b.to > b.from, {
error: "Ketish kelishdan keyin bo'lsin",
path: ["to"],
});
const r = Booking.safeParse({ from: "19:00", to: "18:00" });
console.log(r.error.issues.map((i) => i.message));Konsolda:
(1) ['Invalid input: expected number, received undefined']Vaqt xatosi chiqmadi. Zod 4 qoidasi: obyektda tur xatosi (maydon yo'q, son o'rniga satr) bo'lsa, obyekt darajasidagi refine ishga tushmaydi — chunki u b.guests ni son deb ishonib yozilgan. min kabi qoida xatolari esa to'sqinlik qilmaydi.
Formada bu shunday ko'rinadi: mehmon vaqtni noto'g'ri tanladi, lekin xabar faqat boshqa maydon to'g'rilangandan keyin chiqadi. Kerak bo'lsa, when parametri bilan "qachon ishlasin" ni o'zingiz aytasiz:
import { z } from "zod";
const Booking = z
.object({ from: z.string(), to: z.string(), guests: z.number() })
.refine((b) => b.to > b.from, {
error: "Ketish kelishdan keyin bo'lsin",
path: ["to"],
when: (payload) =>
payload.issues.every(
(i) => i.path[0] !== "from" && i.path[0] !== "to",
),
});
const r = Booking.safeParse({ from: "19:00", to: "18:00" });
console.log(r.error.issues.map((i) => i.message));Konsolda:
(2) ['Invalid input: expected number, received undefined', "Ketish kelishdan keyin bo'lsin"]when — "from va to da xato yo'q bo'lsa, ishlayver". Endi ikkala xabar birga.
5.3 superRefine — bir nechta xato
refine bitta xabar beradi. Buyurtmada bir taom ikki marta yozilgan bo'lsa-chi? Har takror qatorga alohida xato kerak. superRefine — ctx (kontekst) oladi va xohlagancha xato qo'shadi:
import { z } from "zod";
const Order = z
.object({ dishes: z.array(z.string().trim().min(1)) })
.superRefine((order, ctx) => {
order.dishes.forEach((dish, i) => {
if (order.dishes.indexOf(dish) !== i) {
ctx.issues.push({
code: "custom",
input: dish,
path: ["dishes", i],
message: `«${dish}» yuqorida bor`,
});
}
});
});
const dishes = ["osh", "manti", "osh", "manti"];
const r = Order.safeParse({ dishes });
const show = (i) => `${i.path.join(".")}: ${i.message}`;
console.log(r.error.issues.map(show));Konsolda:
(2) ['dishes.2: «osh» yuqorida bor', 'dishes.3: «manti» yuqorida bor']path: ["dishes", 2] — massivning uchinchi elementi. Formada bu errors.dishes[2].message bo'ladi — xato aynan takror qator ostida (dinamik maydonlar darsida). ctx.issues.push({…}) — Zod 4 uslubi; eski kodda ctx.addIssue({…}) ni ko'rasiz, u ham ishlaydi.
refine va superRefine async funksiya ham qabul qiladi ("bu nom serverda bormi?"). Unda safeParse o'rniga safeParseAsync kerak. Bu — RHF III darsida, vazifalar dagi ko'p qadamli formada.
flowchart LR
A["Kirish<br/>(satrlar)"] --> P["preprocess<br/>'' → undefined"]
P --> T["Tur va qoidalar<br/>coerce, min, regex"]
T --> TR["transform<br/>trim, raqamlar"]
TR --> R["refine /<br/>superRefine"]
R --> O["Chiqish<br/>(z.output)"]Zanjir tartibi bo'yicha: preprocess — eng oldin, refine — oxirida. Obyekt refine i esa barcha maydonlar tekshirilib bo'lgach ishlaydi.
Tekshirib ko'ring: "Parol" va "Parolni takrorlang" maydonlari. Xato qaysi maydon ostida chiqishi kerak va buni qanday yozasiz?
Javob
"Parolni takrorlang" ostida: mehmon aynan shu maydonni noto'g'ri to'ldirgan deb hisoblanadi. Obyekt sxemasiga .refine((v) => v.password === v.repeat, { error: "Parollar mos emas", path: ["repeat"] }). path siz xato obyektga tegishli bo'lib qoladi va forma uni maydon ostida ko'rsatmaydi.
6. O'zbekcha xabarlar
6.1 Uch daraja
Zod'ning xabarlarini uch joyda o'zgartirish mumkin:
| Daraja | Qanday | Qachon |
|---|---|---|
| har qoida | .min(2, "Ism kamida 2 harf") yoki { error: "…" } |
asosiy yo'l — eng aniq matn |
| tur xatosi | z.number({ error: (iss) => … }) |
"bo'sh" va "noto'g'ri tur" ni ajratish |
| global | z.config(z.locales.uz()) |
xabar yozilmagan hamma joy |
Funksiya ko'rinishidagi error qiymatni ko'rib xabar tanlaydi:
import { z } from "zod";
const Guests = z.number({
error: (iss) =>
iss.input === undefined ? "Maydonni to'ldiring" : "Son yozing",
});
console.log(Guests.safeParse(undefined).error.issues[0].message);
console.log(Guests.safeParse("to'rt").error.issues[0].message);Konsolda:
Maydonni to'ldiring
Son yozing6.2 Tayyor tarjima: z.locales.uz()
Zod 4 da o'zbekcha tarjima bor (Zod va turni sxemadan chiqarish darsida ko'rgansiz). U natija oynasida yo'q (u yerda faqat en), shuning uchun bu blok Node'da tekshirilgan:
import { z } from "zod";
z.config(z.locales.uz());
const Name = z.string().min(2);
console.log(Name.safeParse("A").error?.issues[0]?.message);Konsolda:
Juda kichik: kutilgan string >=2 belgi bo‘lishi kerakIkki sabab bilan biz global tarjimaga tayanmaymiz. Birinchisi — matn texnik ("kutilgan string >=2 belgi"), mehmon uchun emas. Ikkinchisi — u ‘ belgisini ishlatadi (bo‘lishi), kursda va vazifalar da esa faqat '. Global tarjima — "zaxira": siz yozishni unutgan joyda inglizcha chiqmasin. Asosiy matnlar — har qoidaning o'zida.
7. zod/mini — vazifalar dagi ko'rinish
7.1 Bir xil qoidalar, boshqa yozuv
vazifalar klassik zod emas, zod/mini ni ishlatadi (API qatlami darsidagi qaror: klassik +24.2 KB, mini +5.8 KB gzip). Mini'da metodlar zanjiri yo'q — qoidalar .check(...) ichida funksiya sifatida:
Klassik zod |
zod/mini |
|---|---|
z.string().trim().min(1, "…") |
z.string().check(z.trim(), z.minLength(1, "…")) |
.max(100, "…") |
z.maxLength(100, "…") |
.refine(fn, "…") |
z.refine(fn, "…") (.check ichida) |
S.optional() |
z.optional(S) |
S.transform(fn) |
z.pipe(S, z.transform(fn)) |
obj.superRefine(fn) |
obj.check(z.superRefine(fn)) |
Bitta farq bor. Mini'da tarjimalar sukut bo'yicha yuklanmaydi — xabar yozilmagan qoida faqat "Invalid input" deydi:
import { z } from "zod/mini";
const Name = z.string().check(z.minLength(2));
console.log(Name.safeParse("A").error.issues[0].message);
const Named = z.string().check(z.minLength(2, "Ism kamida 2 harf"));
console.log(Named.safeParse("A").error.issues[0].message);Konsolda:
Invalid input
Ism kamida 2 harfBu kichik hajmning narxi: tarjima matnlari to'plamga kirmaydi. Xulosa — mini'da har qoidaga xabar yozing. vazifalar aynan shunday qiladi.
8. Vazifalar qadami: tahrir formasining sxemasi (holat 32)
8.1 sxemalar/vazifa-formasi.ts
Oldingi darsda tahrir formasining skeletini yozdik — register("matn") qoidasiz. Qoidalar shu faylda (kanon, to'liq):
// vazifa-formasi.ts — formalar sxemasi (19/#30–#31): React Hook
// Form shu sxemani resolver orqali ishlatadi. Qoidalar — ro'yxat
// formasidagi (holat.ts xatoniTop) bilan bir xil matnlar: bo'sh,
// juda uzun, takror.
// zod/mini (19/#12 qarori — hajm): zanjir o'rniga .check(...)
import { z } from "zod/mini";
import { Vazifa } from "../vazifa.ts";
/** Matn: chetlari kesiladi (transform), keyin tekshiriladi */
export function matnSxemasi(takrormi: (matn: string) => boolean) {
return z.string().check(
// z.trim() — qiymatni O'ZGARTIRADI (overwrite): forma
// natijasida kesilgan matn; keyingi tekshiruvlar
// kesilganiga qaraydi
z.trim(),
z.minLength(1, "Vazifa matnini yozing."),
z.maxLength(
Vazifa.MAKS_UZUNLIK,
`Matn juda uzun (ko'pi bilan ${Vazifa.MAKS_UZUNLIK} belgi).`,
),
z.refine(
(matn) => !takrormi(matn),
"Bu vazifa ro'yxatda allaqachon bor.",
),
);
}
/**
* Tahrirlash formasi (19/#31–#32). takrormi — shu vazifadan
* BOSHQA vazifada shu matn bormi (sxema ro'yxatni bilmaydi —
* tashqaridan)
*/
export function tahrirSxemasi(takrormi: (matn: string) => boolean) {
return z.object({
matn: matnSxemasi(takrormi),
bajarildi: z.boolean(),
});
}
export type TahrirQiymati = z.output<ReturnType<typeof tahrirSxemasi>>;Bugungi darsning to'rt g'oyasi shu faylda:
z.trim()— overwrite. Mehmon" Sut olish "yozsa, forma natijasida"Sut olish". Keyingi uch qoida kesilgan matnni ko'radi:" "→""→ "Vazifa matnini yozing.".- Xabarlar — ro'yxat sahifasidagi tezkor formaniki bilan bir xil (
holat.tsdagixatoniTop: "Vazifa matnini yozing.", "Bu vazifa ro'yxatda allaqachon bor."). Ikki formada bitta xato ikki xil yozilsa, foydalanuvchi chalkashadi. z.refine+ tashqi funksiya. "Takrormi?" uchun ro'yxat kerak, sxema esa ro'yxatni bilmaydi. Shuning uchun sxema — funksiya:matnSxemasi(takrormi). Uni chaqirgan joy (forma) ro'yxatni biladi va tekshiruvchini beradi. Shu tufayli sxemani ro'yxatsiz, sof funksiya bilan test qilish mumkin.z.output—TahrirQiymati, ya'ni saqlash funksiyasi oladigan tur. Kirish va chiqish bu yerda bir xil ko'rinadi (ikkalasi{ matn: string; bajarildi: boolean }), lekin chiqishda matn kesilgan — qiymat farq qiladi.
Vazifa.MAKS_UZUNLIK — 100 (static a'zolar darsidagi statik maydon). Qoida bitta joyda: server javobini tekshiradigan api-sxema.ts ham shu sonni ishlatadi.
8.2 Test: tekshiruv/vazifa-formasi.test.js
Sxema React'siz test qilinadi — oddiy node:test (kanon):
// tekshiruv/vazifa-formasi.test.js — forma sxemasi (19/#30): trim
// (transform), bo'sh, uzun, takror (refine — tashqi funksiya bilan)
import { describe, test } from "node:test";
import assert from "node:assert/strict";
import {
matnSxemasi,
tahrirSxemasi,
} from "../src/sxemalar/vazifa-formasi.ts";
const xabar = (natija) =>
natija.success ? null : natija.error.issues.map((i) => i.message);
describe("vazifa formasi sxemasi", () => {
const sxema = matnSxemasi((m) => m.toLowerCase() === "non olish");
test("chetlari kesiladi — natija kesilgan matn", () => {
assert.deepEqual(sxema.safeParse(" Sut olish "), {
success: true,
data: "Sut olish",
});
});
test("bo'sh va faqat bo'shliq — bitta xabar", () => {
assert.deepEqual(xabar(sxema.safeParse(" ")), [
"Vazifa matnini yozing.",
]);
});
test("101 belgi — uzun; 100 — to'g'ri", () => {
assert.deepEqual(xabar(sxema.safeParse("x".repeat(101))), [
"Matn juda uzun (ko'pi bilan 100 belgi).",
]);
assert.equal(sxema.safeParse("x".repeat(100)).success, true);
});
test("takror (kesilgandan keyin tekshiriladi)", () => {
assert.deepEqual(xabar(sxema.safeParse(" NON OLISH ")), [
"Bu vazifa ro'yxatda allaqachon bor.",
]);
});
test("tahrir: bajarildi — faqat boolean", () => {
const t = tahrirSxemasi(() => false);
assert.equal(
t.safeParse({ matn: "A", bajarildi: "ha" }).success,
false,
);
assert.deepEqual(t.parse({ matn: " A ", bajarildi: true }), {
matn: "A",
bajarildi: true,
});
});
});E'tibor bering: "bo'sh va faqat bo'shliq — bitta xabar". " " uchun minLength(1) buzildi, refine ham ishlashi mumkin edi. Lekin takrormi("") — false, shuning uchun xabar bitta. Test buni qotirib qo'yadi. To'rtinchi test — tartibning isboti: " NON OLISH " avval kesiladi, keyin takror deb topiladi (takrormi kichik harf bilan solishtiradi, xuddi matnBormi kabi).
.ts faylni node:test to'g'ridan import qiladi — Node 24 turlarni o'chirib ishlatadi.
8.3 Tekshiruv
Holat 32 da (kanon natijalar/tekshiruv-32.txt):
ℹ tests 182
ℹ pass 182
ℹ fail 0Bu xom natija «Orqaga» tuzatishidan oldin olingan: 182 = oldingi 177 + shu fayldagi 5. Tuzatish (React Router III) har holatga yana 8 ta test qo'shgan, shuning uchun sizda shu qadamda 190 bo'ladi (oldingi 185 + 5). Commit hali yo'q: sxema formaga kelgusi darsda zodResolver bilan ulanadi.
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| bo'sh maydon "0 kishi" bo'lib o'tdi | z.coerce.number() — "" → 0 |
z.preprocess bilan "" → undefined |
expected number, received string |
formadan satr keldi | z.coerce.number() yoki RHF valueAsNumber |
| ixtiyoriy email bo'sh bo'lsa ham xato | .optional() "" ni qabul qilmaydi |
z.union([z.literal(""), z.email()]) |
| "Parollar mos emas" maydon ostida chiqmaydi | refine da path yo'q |
{ error, path: ["repeat"] } |
| ikki maydon xatosi kech chiqadi | obyektda tur xatosi — refine ishlamadi |
when yoki maydonlarni avval to'g'rilash |
| "Invalid input" — boshqa hech narsa | zod/mini, xabar yozilmagan |
har qoidaga xabar |
"false" → true |
z.coerce.boolean() — Boolean("false") |
z.stringbool() yoki checkbox'dan boolean |
string.email() eskirgan ogohlantirishi |
Zod 3 yozuvi | z.email() |
10. Mashqlar
Mashqlar kurs/mashqlar/19/30-zod/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): natijani ayting
Har qator nima beradi? Natija (son) yoki "xato" deb yozing.
z.coerce.number().parse("7")→z.coerce.number().parse("")→z.coerce.number().safeParse("yetti").success→z.string().optional().safeParse("").success→z.email().optional().safeParse("").success→
Yechim
- 7 — satr songa aylandi.
- 0 —
Number("")—0. Formadagi eng ko'p uchraydigan tuzoq. - false —
Number("yetti")—NaN, ZodNaNni son deb qabul qilmaydi. - true —
""ham satr;optionalfaqatundefinedga ruxsat qo'shadi. - false —
""email emas.optionaluni "yo'q" deb hisoblamaydi.
Yechim fayli shu beshtasini konsolga chiqaradi.
2-mashq (o'rta): bron sxemasi
Bron formasi uchun Booking sxemasini yozing:
name— kesiladi, kamida 2 harf: "Ism kamida 2 harf".phone—+998 90 000 00 00ko'rinishi ("Namuna: +998 90 000 00 00"), natija — faqat raqamlar.guests— bo'sh bo'lsa "Nechta mehmon?", butun son 1–12.email— ixtiyoriy: bo'sh satr bo'lsa natijadaundefined, aks holda to'g'ri email ("Email noto'g'ri").
Boshlang'ich kodda sinov ma'lumoti bor:
import { z } from "zod";
const Booking = z.object({
// shu yerga
});
const form = {
name: " Malika ",
phone: "+998 90 000 00 00",
guests: "4",
email: "",
};
console.log(Booking.safeParse(form));Konsolda:
{success: true, data: {}}Bo'sh z.object({}) noma'lum kalitlarni tashlab yuboradi — shuning uchun data bo'sh.
Yechim
import { z } from "zod";
const emptyToUndefined = (v) => (v === "" ? undefined : v);
const PHONE = /^\+998 \d{2} \d{3} \d{2} \d{2}$/;
const Booking = z.object({
name: z.string().trim().min(2, "Ism kamida 2 harf"),
phone: z
.string()
.trim()
.regex(PHONE, "Namuna: +998 90 000 00 00")
.transform((s) => s.replace(/\D/g, "")),
guests: z.preprocess(
emptyToUndefined,
z.coerce
.number({ error: "Nechta mehmon?" })
.int("Butun son yozing")
.min(1, "Kamida 1 kishi")
.max(12, "Ko'pi bilan 12 kishi"),
),
email: z.preprocess(
emptyToUndefined,
z.email("Email noto'g'ri").optional(),
),
});
const form = {
name: " Malika ",
phone: "+998 90 000 00 00",
guests: "4",
email: "",
};
console.log(Booking.safeParse(form));
const bad = Booking.safeParse({ ...form, guests: "", email: "ali@" });
console.log(z.flattenError(bad.error).fieldErrors);Konsolda:
{success: true, data: {name: 'Malika', phone: '998900000000', guests: 4, email: undefined}}
{guests: ['Nechta mehmon?'], email: ["Email noto'g'ri"]}emptyToUndefined ikki marta ishlatildi — bitta yordamchi funksiya, ikki maydon. email: undefined natijada bor: preprocess "" ni undefined qildi, optional uni qabul qildi.
3-mashq (qiyin): buyurtma — bir nechta xato
Buyurtma: items — { dish, qty } lar massivi. Qoidalar:
dish—"osh","manti"yoki"lagmon"(z.enum);qty— formadan satr, butun son 1–10.- Bir taom ikki marta bo'lmasin: takror qatorning
dishmaydoniga "Bu taom yuqorida bor". - Jami porsiya 20 dan oshmasin:
itemsning o'ziga "Bir buyurtmada ko'pi bilan 20 porsiya".
Ishora: 2 va 3 — superRefine da, ctx.issues.push. Massiv elementi maydoniga yo'l — ["items", i, "dish"]. qty uchun z.coerce.number() yetarli: bo'sh qator bu mashqda yo'q.
Yechim
import { z } from "zod";
const Item = z.object({
dish: z.enum(["osh", "manti", "lagmon"], {
error: "Taomni tanlang",
}),
qty: z.coerce.number().int().min(1).max(10, "Ko'pi bilan 10"),
});
const Order = z
.object({ items: z.array(Item).min(1, "Kamida bitta taom") })
.superRefine((order, ctx) => {
const dishes = order.items.map((item) => item.dish);
dishes.forEach((dish, i) => {
if (dishes.indexOf(dish) !== i) {
ctx.issues.push({
code: "custom",
input: dish,
path: ["items", i, "dish"],
message: "Bu taom yuqorida bor",
});
}
});
const total = order.items.reduce((sum, it) => sum + it.qty, 0);
if (total > 20) {
ctx.issues.push({
code: "custom",
input: total,
path: ["items"],
message: "Bir buyurtmada ko'pi bilan 20 porsiya",
});
}
});
const r = Order.safeParse({
items: [
{ dish: "osh", qty: "8" },
{ dish: "manti", qty: "9" },
{ dish: "osh", qty: "5" },
],
});
const show = (i) => `${i.path.join(".")}: ${i.message}`;
console.log(r.error.issues.map(show));
console.log(Order.parse({ items: [{ dish: "manti", qty: "3" }] }));Konsolda:
(2) ['items.2.dish: Bu taom yuqorida bor', "items: Bir buyurtmada ko'pi bilan 20 porsiya"]
{items: [{dish: 'manti', qty: 3}]}Ikkita xato bir vaqtda — refine bu yerda yetmasdi. Ikkinchi qator — to'g'ri buyurtma: qty endi son (3), satr emas. reduce — massivni yig'ish.
4-mashq: Vazifalar qadami
src/sxemalar/vazifa-formasi.ts va tekshiruv/vazifa-formasi.test.js ni kanondagidek yozing. npm test — node:test 190 (5 tasi yangi; «Orqaga» tuzatishigacha — 182), hammasi pass.
Yechim
Tekshiruv ro'yxati:
npm test—ℹ tests 190 · ℹ pass 190(oldin 185; «Orqaga» tuzatishigacha 177 → 182).- Testni ataylab buzing:
z.trim()niz.minLengthdan keyinga o'tkazing. "bo'sh va faqat bo'shliq" testi yiqiladi —" "uzunligi 3,minLength(1)o'tadi. Tartib qaytarilgach yana yashil. npm run tip—TahrirQiymati{ matn: string; bajarildi: boolean }.- Commit hali qilinmaydi — forma RHF + Zod darsida ulanadi.
11. Real ishda
- Bitta sxema — ikki tomon. Next.js server action'ida (Server Actions II) yoki Express'da (Zod serverda) aynan shu sxema qayta tekshiradi. Brauzerdagi tekshiruv mehmon uchun tez javob, serverdagi — himoya.
coerce— query va env uchun ham: URL parametri (?sahifa=2) va muhit o'zgaruvchilari ham satr.z.coerce.number()ular uchun ham kerak, lekin "bo'sh" holatini doim o'ylang.- Muqobillar: Valibot va ArkType xuddi shu g'oyalarni beradi — Muqobillar darsida o'lchov bilan.
- Intervyu savollari: "
z.inputvaz.outputfarqi?", "refinevasuperRefinefarqi?", "coercening xavfi nima?", "nega obyektrefinei ba'zan ishlamaydi?".
Xulosa
- Formadan hamma narsa satr keladi, bo'sh maydon —
"".z.coerce.number()uni0qiladi —z.preprocessbilan""→undefined. optionalfaqatundefinedni qabul qiladi; bo'sh ixtiyoriy maydon —z.union([z.literal(""), …])yokipreprocess.transform— tekshiruvdan keyin o'zgartiradi; sxemaning kirish (z.input) va chiqish (z.output) turlari farq qiladi.refine+path— ikki maydonni solishtirish; tur xatosi bo'lsa ishlamaydi (when).superRefine— bir nechta xato,ctx.issues.push.- Xabarlar — har qoidaning o'zida, o'zbekcha.
zod/minida xabarsiz qoida faqat "Invalid input" deydi.
Keyingi dars: RHF + Zod: resolver va umumiy sxemalar — sxemani zodResolver bilan formaga ulash, kirish/chiqish turlari formada, server xatosini setError bilan ko'rsatish.
Manbalar
- Zod 4: Basic usage, Defining schemas (coerce, optional, transform, refine, superRefine) — zod.dev/api (2026-10 holatiga, v4.6.5)
- Zod 4: Customizing errors (
errorparametri,z.config, locales) — zod.dev/error-customization - Zod 4: Formatting errors (
z.flattenError,z.prettifyError) — zod.dev/error-formatting - Zod Mini — zod.dev/packages/mini
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!