IlmHamroh
JavaScript Full-stack/15-qism. TypeScript43/47-dars21 daqiqa
Mundarija (36)

Zod va TypeScript: turni sxemadan chiqarish — z.object, z.infer, safeParse

Qisqacha: Zod — sxema kutubxonasi: ma'lumot shaklini bir marta z.object({ … }) bilan yozasiz va undan ikki narsa olasiz — ish vaqtidagi tekshiruv (Schema.parse(data)) va TypeScript turi (z.infer<typeof Schema>). Tur va tekshiruv endi bitta joyda, ular hech qachon ajralib qolmaydi. parse xato tashlaydi, safeParse esa { success, data | error } qaytaradi. Kutubxonaning narxi bor: bundlersiz brauzerda Zod 4.6.5 — 95 modul va 233,7 KB, shuning uchun vazifalar da qo'lda yozilgan tekshiruv qoldi.

Bu darsda

  • Zod 4.6.5 ni o'rnatib, z.object, z.string(), z.number(), z.enum bilan sxema yozasiz.
  • z.infer bilan turni sxemadan chiqarasiz va bu "sehr" qanday ishlashini 20 qatorlik o'z kutubxonangizda ko'rasiz.
  • parse va safeParse ni, xato xabarlarini (o'zbekcha ham) ishlata olasiz.
  • transform, z.coerce va z.input/z.output farqini bilasiz — forma va API uchun.
  • Standard Schema va Valibot'ni tanishasiz va kutubxona narxini o'lchangan raqamlar bilan baholaysiz.

Oldin bilishingiz kerak: API ma'lumotlarini tiplash, infer kalit so'zi, Utility types, Uchinchi tomon kutubxonalarini tiplash.

1. Nega bu kerak?

O'tgan darsda menyuni ikki marta yozdik:

ts
// tur
type MenuItem = { id: number; nom: string; narx: number };
// tekshiruv
function parseMenu(data: unknown): MenuItem[] { /* 20 qator */ }

Bir oydan keyin Jasur aka so'radi: "Menyuga turkum qo'shaylik". Sardor type MenuItem ga turkum ni qo'shdi. tsc darhol parseMenu ning return qatorini qizil qildi — yaxshi. Lekin keyinroq u nom ning minimal uzunligini o'zgartirdi — faqat parseMenu da. Tur buni bilmaydi va bilishi ham shart emas. Ikki joy — ikki haqiqat: ertami-kechmi ular ajralib qoladi.

Sxema kutubxonasi g'oyasi: shaklni bir marta yozish. Tekshiruv sxemadan ishlaydi, tur sxemadan chiqariladi.

Hayotiy o'xshatish: oshxonada retsept kartasi va ombor ro'yxati alohida yuritilsa, ertaga kartada "zira" bor, omborda yo'q. Bitta jadvaldan ham retsept, ham xarid ro'yxati chiqarilsa — ular doim mos.

2. G'oya: 20 qatorlik "sxema kutubxonasi"

Zod'ni o'rnatishdan oldin uning yuragini o'zimiz yozamiz. Sxema — bu shunchaki unknown oladigan va aniq tur qaytaradigan funksiya (o'tgan darsdagi parse… ning o'zi):

ts
type Schema<T> = (value: unknown) => T;
type Infer<S> = S extends Schema<infer T> ? T : never;

const str: Schema<string> = (value) => {
  if (typeof value !== "string") throw new TypeError("satr emas");
  return value;
};
const num: Schema<number> = (value) => {
  if (typeof value !== "number") throw new TypeError("son emas");
  return value;
};

function object<S extends Record<string, Schema<unknown>>>(shape: S) {
  return (value: unknown): { [K in keyof S]: Infer<S[K]> } => {
    if (typeof value !== "object" || value === null) {
      throw new TypeError("obyekt emas");
    }
    const source = value as Record<string, unknown>;
    const result: Record<string, unknown> = {};
    for (const key of Object.keys(shape)) {
      result[key] = shape[key]!(source[key]);
    }
    return result as { [K in keyof S]: Infer<S[K]> };
  };
}

const MenuItem = object({ nom: str, narx: num });
type MenuItem = Infer<typeof MenuItem>;

const osh: MenuItem = MenuItem({ nom: "Osh", narx: 35000, x: 1 });
console.log(osh); // { nom: 'Osh', narx: 35000 }

Bu kodda 15-qismning uch darsi uchrashdi:

  • Infer<S> — infer kalit so'zi: "agar S — Schema<T> bo'lsa, T ni ol".
  • { [K in keyof S]: Infer<S[K]> } — Mapped types: sxemaning har kaliti uchun o'sha kalitdagi sxemaning natija turi.
  • typeof MenuItem — qiymatdan tur: sxema — qiymat (funksiya), tur esa undan olinadi.

Nima bo'ldi? tsc type MenuItem = Infer<typeof MenuItem> ni qadamma-qadam hisoblaydi:

Qadam Nimani hisoblaydi Natija
1 object({ nom: str, narx: num }) dagi S { nom: Schema<string>; narx: Schema<number> }
2 K = "nom": Infer<S["nom"]> = Infer<Schema<string>> string
3 K = "narx": Infer<Schema<number>> number
4 typeof MenuItem — qaytgan funksiyaning turi (value: unknown) => { nom: string; narx: number }
5 Infer<…> funksiya natijasini "tutib oladi" { nom: string; narx: number }

Natija: MenuItem turi — { nom: string; narx: number }, lekin biz uni yozmadik. Sxemaga maydon qo'shsangiz, tur o'zi o'zgaradi. Endi noto'g'ri obyekt yozib ko'ramiz:

ts
type Schema<T> = (value: unknown) => T;
type Infer<S> = S extends Schema<infer T> ? T : never;
declare const str: Schema<string>;
declare const num: Schema<number>;
declare function object<S extends Record<string, Schema<unknown>>>(
  shape: S,
): Schema<{ [K in keyof S]: Infer<S[K]> }>;

const MenuItem = object({ nom: str, narx: num });
type MenuItem = Infer<typeof MenuItem>;
const lagmon: MenuItem = { nom: "Lag'mon", narx: "28 000" };

Kompilyator:

text
TS2322: Type 'string' is not assignable to type 'number'.

TypeScript aytyapti: "string ni number ga berib bo'lmaydi" — narx: "28 000" qatorida.

tsc sxemadan chiqarilgan turdan foydalandi: narx — number. Blokdagi declare — "bunday qiymat bor deb hisobla" degan e'lon (Declaration fayllari darsidan). Bu yerda bizga faqat turlar kerak edi.

Zod — xuddi shu g'oya, faqat 100 dan ortiq tekshiruvchi, chiroyli xato xabarlari va puxta turlar bilan.

Tekshirib ko'ring: Bizning object ga { nom: str, narx: num } berildi. osh da x: 1 qayerga ketdi?

Javob

Natijaga o'tmadi: object faqat shape dagi kalitlarni aylanib chiqadi va yangi obyekt yasaydi. Ortiqcha maydonlar tashlanadi — o'tgan darsdagi oq ro'yxat qoidasi. Zod ham standart holda shunday qiladi.

3. Zod 4: birinchi sxema

3.1 O'rnatish

kurs/mashqlar/15/ papkasida:

bash
npm install --save-exact zod@4.6.5

Zod — oddiy dependency (devDependency emas): u dastur ishlayotganda ham kerak, chunki tekshiruv ish vaqtida bo'ladi. Turlari paketning ichida — @types/zod kerak emas (Uchinchi tomon kutubxonalarini tiplash). Bu darsning Zod misollari saytda «Ishga tushir» tugmasiz (zod sayt sahifasida yo'q) — ularni kurs/mashqlar/15/43-zod/ da node fayl.ts bilan bajaring. Biz har birini tsc 7.0.2 va Node 24.21.0 da tekshirdik.

3.2 z.object va z.infer

ts
import { z } from "zod";

const MenuItem = z.object({
  id: z.number().int().positive(),
  nom: z.string().min(1),
  narx: z.number().nonnegative(),
  turkum: z.enum(["taom", "ichimlik"]),
});
type MenuItem = z.infer<typeof MenuItem>;

const raw: unknown = JSON.parse(
  '{"id":1,"nom":"Osh","narx":35000,"turkum":"taom","x":1}',
);
const osh: MenuItem = MenuItem.parse(raw);
console.log(osh);

Konsolda:

text
{ id: 1, nom: 'Osh', narx: 35000, turkum: 'taom' }
  • z.number().int().positive() — zanjir: son, butun, musbat. Har qadam tekshiruvga yana bir shart qo'shadi.
  • z.enum(["taom", "ichimlik"]) — faqat shu ikki satrdan biri; turi — "taom" | "ichimlik" (literal union).
  • MenuItem nomi ikki marta: biri qiymat (sxema), biri tur. TypeScript'da bunga ruxsat bor — qiymatlar va turlar alohida "nomlar maydonida" yashaydi. Zod hujjati ham shunday yozishni tavsiya qiladi.
  • Ortiqcha x tushib qoldi — z.object standart holda noma'lum kalitlarni olib tashlaydi.

z.infer qanday tur chiqarganini tsc ning o'zi aytadi. Noto'g'ri maydonga murojaat qildik:

ts
MenuItem.parse(raw).price;
text
43-zod/menu.ts(13,21): error TS2339: Property 'price' does not exist on type '{ id: number; nom: string; narx: number; turkum: "ichimlik" | "taom"; }'.

Xabardagi tur — sxemadan chiqarilgan: { id: number; nom: string; narx: number; turkum: "ichimlik" | "taom"; }. Bitta qator ham qo'lda yozilmagan.

3.3 Asosiy qurilish bloklari

Zod Tur Izoh
z.string(), z.number(), z.boolean() string, number, boolean
z.array(S) S[]
z.enum(["a", "b"]) "a" | "b"
S.optional() / S.nullable() T | undefined / T | null
z.union([A, B]) A | B

z.object ichida .optional() li maydon — ixtiyoriy kalit (izoh?: string). Discriminated union uchun ham alohida vosita bor: z.discriminatedUnion("ok", [A, B]).

4. parse va safeParse

4.1 parse — xato tashlaydi

ts
import { z } from "zod";

const Price = z.number().nonnegative();
try {
  Price.parse("35 000");
} catch (error) {
  console.log(error instanceof z.ZodError);
  if (error instanceof z.ZodError) {
    console.log(error.issues[0]?.message);
  }
}

Konsolda:

text
true
Invalid input: expected number, received string

parse muvaffaqiyatli bo'lsa — tekshirilgan qiymat, bo'lmasa — ZodError. Uning issues massivida har muammo: code ("invalid_type"), path (qaysi maydon), message.

4.2 safeParse — natija obyekti

Ko'pincha xatoni tashlash emas, ko'rsatish kerak (formada). safeParse hech qachon tashlamaydi:

ts
import { z } from "zod";

const MenuItem = z.object({
  id: z.number().int().positive(),
  nom: z.string().min(1),
  narx: z.number().nonnegative(),
  turkum: z.enum(["taom", "ichimlik"]),
});

const result = MenuItem.safeParse({
  id: 1, nom: "", narx: "35 000", turkum: "salat",
});
if (result.success) {
  console.log(result.data.nom);
} else {
  console.log(result.error.issues.length);
  console.log(z.prettifyError(result.error));
}

Konsolda:

text
3
✖ Too small: expected string to have >=1 characters
  → at nom
✖ Invalid input: expected number, received string
  → at narx
✖ Invalid option: expected one of "taom"|"ichimlik"
  → at turkum

result — discriminated union: success: true da data, false da error. Bu o'tgan darsdagi ApiResponse<T> ning o'zi. success ni tekshirmasdan result.data.nom yozsangiz, tsc:

text
43-zod/menu.ts(12,13): error TS18048: 'result.data' is possibly 'undefined'.

Nega 'data' does not exist emas, possibly 'undefined'? Zod natija turini shunday yozgan: muvaffaqiyatsiz holatda ham data kaliti bor, faqat undefined. Shu tuzilishni o'zimiz yozib, Zod'siz ko'ramiz:

ts
type SafeResult<T> =
  | { success: true; data: T; error?: undefined }
  | { success: false; data?: undefined; error: Error };

declare const result: SafeResult<{ nom: string }>;
console.log(result.data.nom);

Kompilyator:

text
TS18048: 'result.data' is possibly 'undefined'.

TypeScript aytyapti: "result.data undefined bo'lishi mumkin".

Ikkala holatda ham data kaliti borligi uchun result.data ga murojaat mumkin, lekin turi { nom: string } | undefined. result.data?.nom — undefined bo'lishi mumkin bo'lgan qiymat. To'g'risi esa success ni tekshirish: shunda data aniq bor.

Zod hamma xatoni bir vaqtda yig'adi (birinchisida to'xtamaydi): uchta buzuq maydon — uchta muammo. Formada foydalanuvchiga hammasini birdan ko'rsatish mumkin. z.prettifyError — o'qish uchun qulay matn.

4.3 O'zbekcha xabarlar

Zod 4 da tayyor tarjimalar bor, o'zbekchasi ham — z.locales.uz(). Kerakli joyga o'z matningizni ham yozish mumkin:

ts
import { z } from "zod";

z.config(z.locales.uz());

const Order = z.object({
  dish: z.string().trim().min(1, "Taom nomi bo'sh"),
  quantity: z.number().int().min(1),
});
const result = Order.safeParse({ dish: "  ", quantity: 0 });
if (!result.success) {
  console.log(z.prettifyError(result.error));
}

Konsolda:

text
✖ Taom nomi bo'sh
  → at dish
✖ Juda kichik: kutilgan number >=1
  → at quantity

z.config — butun dastur uchun, bir marta. "Taom nomi bo'sh" — shu tekshiruvning o'z matni. E'tibor bering: Zod'ning o'zbekcha tarjimasi ba'zi so'zlarda ‘ belgisini ishlatadi (Noto‘g‘ri). Bizning kursda faqat oddiy ' — matn sizga muhim bo'lsa, o'zingiz yozing.

Tekshirib ko'ring: API javobini tekshirishda parse yoki safeParse — qaysi biri? Forma uchun-chi?

Javob

API javobi buzuq bo'lsa — bu kutilmagan holat, server bilan kelishuv buzilgan: parse qulay, xato yuqoriga ko'tariladi va bitta joyda ushlanadi. Forma esa tez-tez noto'g'ri to'ldiriladi — bu normal holat: safeParse va issues dan har maydon yonida xabar. Qoida Async kod va xatolarni tiplash darsida: kutilgan xato — qiymat, kutilmagani — throw.

5. Ma'lumotni o'zgartirish: transform va coerce

5.1 Sana satrdan Date ga

O'tgan darsda JSON'da sana — satr ekanini aytdik. Zod tekshiradi va aylantiradi:

ts
import { z } from "zod";

const Order = z.object({
  dish: z.string().trim().min(1),
  quantity: z.coerce.number().int().min(1),
  createdAt: z.iso.datetime().transform((s) => new Date(s)),
});
type OrderInput = z.input<typeof Order>;
type Order = z.output<typeof Order>;

const order: Order = Order.parse({
  dish: " Osh ", quantity: "2", createdAt: "2026-10-06T10:00:00Z",
});
const hour = order.createdAt.getUTCHours();
console.log(order.dish, order.quantity, hour);

Konsolda:

text
Osh 2 10
  • z.string().trim() — avval bo'sh joylarni kesadi, keyin tekshiradi.
  • z.coerce.number() — kirishni Number(…) bilan songa aylantiradi: formadan kelgan "2" → 2. Ehtiyot bo'ling: Number("") — 0, Number(null) — 0. Shuning uchun keyin .min(1).
  • z.iso.datetime() — ISO formatdagi sana-vaqt satri; .transform(…) — tekshiruvdan keyin Date ga aylantiradi.

Endi sxemaning ikki turi bor: kirish va chiqish. z.input<typeof Order> — createdAt: string, quantity: unknown (coerce hamma narsani oladi). z.output (u z.infer bilan bir xil) — createdAt: Date, quantity: number. API klient kirishni yuboradi, ilova chiqishni ishlatadi.

5.2 Forma va API — bitta sxema

TypeScript va DOM darsidagi parseOrder(data: FormData) ni eslang — 20 qator. Zod bilan:

ts
import { z } from "zod";

const OrderForm = z.object({
  dish: z.enum(["osh", "manti", "lagmon"]),
  quantity: z.coerce.number().int().min(1),
  note: z.string().trim().default(""),
});

const data = new FormData();
data.append("dish", "manti");
data.append("quantity", "3");
const order = OrderForm.parse(Object.fromEntries(data));
console.log(order); // { dish: 'manti', quantity: 3, note: '' }

Object.fromEntries(data) — FormData ni oddiy obyektga aylantiradi. .default("") — maydon yo'q bo'lsa, bo'sh satr. Xuddi shu sxema serverda ham ishlatilishi mumkin (24-qismdan keyin Node serverda) — forma va server bitta qoidani tekshiradi.

API klientda esa o'tgan darsdagi getJson(response, parse) ga sxemaning parse metodini berasiz: getJson(response, MenuItem.parse). Uning turi — (data: unknown) => MenuItem, ya'ni aynan kutilgan parse funksiyasi.

5.3 .brand() — tekshirilgan qiymatga tamg'a

Branded types darsida "brend" turni qo'lda yozgan edik: oddiy son bilan adashib ketmaydigan son. Zod buni sxemaning o'zida beradi — .brand():

ts
import { z } from "zod";

const OrderId = z.number().int().positive().brand<"OrderId">();
type OrderId = z.infer<typeof OrderId>;

function cancel(id: OrderId): string {
  return `Buyurtma #${id} bekor qilindi`;
}
const id = OrderId.parse(15);
console.log(cancel(id)); // Buyurtma #15 bekor qilindi

cancel(15) deb yozsangiz, tsc (biz sinadik):

text
error TS2345: Argument of type 'number' is not assignable to parameter of type 'number & $brand<"OrderId">'.
  Type 'number' is not assignable to type '$brand<"OrderId">'.

TypeScript aytyapti: "oddiy number ni number & $brand<"OrderId"> (tamg'ali son) kutgan parametrga berib bo'lmaydi". OrderId turidagi qiymatni olishning yagona yo'li — OrderId.parse(…), ya'ni tekshiruvdan o'tkazish. Funksiya imzosi "menga tekshirilgan id ber" deydi, va tsc buni kafolatlaydi. Ishlash paytida brend yo'q: id — oddiy 15.

6. Standard Schema va Valibot

6.1 Standard Schema — umumiy "rozetka"

Sxema kutubxonalari ko'p: Zod, Valibot, ArkType. Forma kutubxonalari, server freymvorklari har biriga alohida ulanishni yozishni xohlamaydi. Shuning uchun ular kelishib, Standard Schema degan umumiy interfeys yaratdi: har sxemada ~standard xususiyati bor, ichida validate(value) funksiyasi. Turlari — @standard-schema/spec paketida (1.1.0, faqat turlar).

Bitta funksiya — istalgan kutubxonaning sxemasi bilan:

ts
import type { StandardSchemaV1 } from "@standard-schema/spec";
import { z } from "zod";
import * as v from "valibot";

function check<T>(
  schema: StandardSchemaV1<unknown, T>,
  data: unknown,
): T {
  const result = schema["~standard"].validate(data);
  if (result instanceof Promise) throw new TypeError("async sxema");
  if (result.issues) {
    const messages = result.issues.map((i) => i.message);
    throw new TypeError(messages.join("; "));
  }
  return result.value;
}

const ZodPrice = z.number().nonnegative();
const ValiPrice = v.pipe(v.number(), v.minValue(0));
console.log(check(ZodPrice, 35000), check(ValiPrice, 5000));
try {
  check(ValiPrice, -1);
} catch (error) {
  console.log((error as Error).message);
}

Konsolda:

text
35000 5000
Invalid value: Expected >=0 but received -1

check hech qaysi kutubxonani "bilmaydi" — faqat standartni. React Hook Form (forma kutubxonasi, 17-qismdan keyin), tRPC kabi vositalar sxemani aynan shunday qabul qiladi: siz kutubxonani tanlaysiz, ular bilan ishlash o'zgarmaydi.

6.2 Valibot — kichik muqobil

Valibot (1.5.0) — o'sha g'oya, boshqa tuzilish. Zod'da metodlar zanjiri (z.number().int()), Valibot'da esa har tekshiruv — alohida funksiya, v.pipe bilan ulanadi:

ts
import * as v from "valibot";

const MenuItem = v.object({
  id: v.pipe(v.number(), v.integer(), v.minValue(1)),
  nom: v.pipe(v.string(), v.minLength(1)),
  narx: v.pipe(v.number(), v.minValue(0)),
  turkum: v.picklist(["taom", "ichimlik"]),
});
type MenuItem = v.InferOutput<typeof MenuItem>;

const result = v.safeParse(MenuItem, {
  id: 4, nom: "Ko'k choy", narx: 5000, turkum: "ichimlik",
});
console.log(result.success && result.output.nom); // Ko'k choy

Funksiyalar alohida bo'lgani uchun bundler ishlatilmaganlarini tashlab yuboradi ("tree shaking", 16-qismda). Natija — juda kichik kod. Bu farq keyingi bo'limda raqamlarda.

7. Kutubxona narxi: vazifalar uchun o'lchov

7.1 Bundlersiz brauzerda

vazifalar da serverRoyxatiniTekshir — qo'lda yozilgan 25 qator. Uni Zod bilan almashtirsak-chi? Biz o'lchadik (Chrome 154, vazifalar dagi CSP va Trusted Types, Pages kabi gzip, sekin tarmoq — 150 ms kechikish va 1,6 Mbit/s, 5 o'lchov medianasi):

Variant Fayllar Uzatildi (gzip) Sahifa tayyor
Qo'lda (paket.ts) 0 0,8 KB 373 ms
Zod 4.6.5 95 233,7 KB 3 729 ms
Valibot 1.5.0 1 15,7 KB 604 ms

vazifalar da bundler yo'q (Build darsi): brauzer har modulni alohida so'raydi. Zod'ning ESM fayllari bir-birini import qiladi — 95 ta fayl zanjiri. Sekin tarmoqda sahifa 0,4 soniya o'rniga 3,7 soniyada tayyor bo'ldi. Bu — ilovaning o'z JavaScript'idan (81 KB) uch baravar katta.

Yana bir topilma: Zod tezlik uchun ichida new Function(…) bilan kod yasaydi (JIT). vazifalar ning Trusted Types siyosati buni taqiqlaydi — brauzer konsolida This document requires 'TrustedScript' assignment va bitta CSP buzilishi chiqdi. Zod buni o'zi aniqlab, sekinroq yo'lga o'tadi, lekin konsol xatosi qoladi. Yechim — z.config({ jitless: true }): JIT o'chadi, buzilish 0.

7.2 Bundler bilan

Bundler bu rasmni o'zgartiradi. Bir xil menyu sxemasini esbuild 0.28.2 bilan bitta faylga yig'dik (--bundle --minify):

Menyu sxemasi bitta faylga yig'ilganda (gzip)
  • Zod 4.6.592,1 KB
  • zod/mini 4.6.56,1 KB
  • Valibot 1.5.01,7 KB

Manba: O'lchandi: esbuild 0.28.2 --bundle --minify, gzip -9, bitta MenuItem sxemasi, 2026-10-06

  • Zod (import { z } from "zod") — metodlar zanjiri klasslarda yashaydi, bundler ishlatilmagan metodlarni ajrata olmaydi: 92,1 KB.
  • zod/mini — Zod'ning o'zi, lekin Valibot uslubida (z.string().check(z.minLength(1))): 6,1 KB.
  • Valibot — 1,7 KB.

7.3 Qaror

Biz qaror qildik: vazifalar da qo'lda yozilgan tekshiruv qoladi (API ma'lumotlarini tiplash qadamidagi serverRoyxatiniTekshir). Sabablar: bundler yo'q, tekshiriladigan shakl bitta (ServerRoyxati) va u allaqachon testlangan. Bitta funksiya uchun 233,7 KB va qo'shimcha 3,3 soniya — juda qimmat. Savol vazifalar bundler'ga (Vite) o'tganda, 16-qismda qayta ochiladi: Zod yoki Valibot o'shanda arzon bo'ladi (Tree shaking va minifikatsiya).

Qoida: kutubxona qo'shishdan oldin uning narxini o'lchang — o'lcham, so'rovlar soni, xavfsizlik siyosatiga ta'siri. Kichik loyihada qo'lda yozilgan 25 qator ko'pincha yetarli. Ko'p sxemali loyihada (formalar, API, server) — sxema kutubxonasi o'zini oqlaydi.

Tekshirib ko'ring: Nega Zod bundlersiz 233,7 KB, bundler bilan 92,1 KB?

Javob

Bundlersiz brauzer zod/index.js dan boshlab import zanjiridagi hamma faylni yuklaydi — 95 ta, kerakmi-yo'qmi. Bundler esa faqat haqiqatan ishlatilgan kodni oladi, bitta faylga birlashtiradi va siqadi (minify): bo'sh joylar, izohlar, uzun nomlar yo'qoladi. Valibot va zod/mini yana kichikroq — ularning tuzilishi ishlatilmagan funksiyalarni tashlashga qulay.

8. Ko'p uchraydigan xatolar

8.1 TS18048: 'result.data' is possibly 'undefined'.

safeParse natijasida success tekshirilmagan. Tuzatish: if (result.success) { … result.data … }.

8.2 Tur va sxemani alohida yozish

type MenuItem = { … } va const MenuItemSchema = z.object({ … }) — yana ikki haqiqat. Tuzatish: type MenuItem = z.infer<typeof MenuItem>.

8.3 z.coerce bilan bo'sh qiymat

z.coerce.number() bo'sh forma maydonini ("") 0 qiladi va o'tkazib yuboradi. Tuzatish: keyin .min(1) yoki .positive(); yoki z.string().min(1) dan keyin transform(Number).

8.4 parse ni try siz

Server javobida ZodError ushlanmay qoladi. Tuzatish: yuqori qatlamda bitta catch yoki safeParse.

8.5 Narxni o'lchamasdan qo'shish

Bundlersiz loyihaga 95 modul. Tuzatish: o'lchang (Network paneli, Lighthouse); kichik loyihada qo'lda tekshiruv yoki Valibot.

9. Mashqlar

1-mashq (oson): Turni o'qing

ts
const Guest = z.object({
  name: z.string(),
  people: z.number().int(),
  phone: z.string().optional(),
});
type Guest = z.infer<typeof Guest>;

Guest turida people ning turi — [:number]. phone majburiymi (ha/yo'q)? [:yo'q].

Yechim

{ name: string; people: number; phone?: string | undefined }. .int() turni o'zgartirmaydi — TypeScript'da "butun son" turi yo'q, faqat number; .int() faqat ish vaqtida tekshiradi. .optional() — ixtiyoriy kalit. Tur va tekshiruv har doim ham bir xil narsani aytmaydi: tekshiruv qattiqroq bo'lishi mumkin.

2-mashq (o'rta): Bron sxemasi

«Bahor» bron formasi: guest (bo'sh emas, bo'sh joylari kesiladi), people (formadan satr keladi, 1 dan 12 gacha butun son), time ("SS:DD", bron 10:00–22:00). Sxema yozing va "19:30", "09:00" bilan sinang. Ishora: z.string().regex(/^\d{2}:\d{2}$/) (RegExp) va .refine((t) => …, "xabar") — o'zingizning tekshiruvingiz.

Yechim
ts
import { z } from "zod";

// "SS:DD" da satr tartibi = vaqt tartibi
const isOpen = (t: string) => t >= "10:00" && t <= "22:00";

const Booking = z.object({
  guest: z.string().trim().min(1, "Mehmon ismi bo'sh"),
  people: z.coerce.number().int().min(1).max(12),
  time: z
    .string()
    .regex(/^\d{2}:\d{2}$/, "Vaqt SS:DD bo'lsin")
    .refine(isOpen, "Bron 10:00–22:00"),
});

const ok = Booking.safeParse({
  guest: "Malika", people: "4", time: "19:30",
});
const early = Booking.safeParse({
  guest: "Bobur", people: "2", time: "09:00",
});
console.log(ok.success, ok.data?.people);
console.log(early.success, early.error?.issues[0]?.message);

Konsolda:

text
true 4
false Bron 10:00–22:00

"09:00" >= "10:00" — satrlarni solishtirish: "SS:DD" formatida ikki xonali bo'lgani uchun alifbo tartibi vaqt tartibi bilan bir xil (regex buni kafolatlaydi). ok.data?.people — 4 son, "4" satr emas (coerce).

3-mashq (qiyin): Amaliy tajriba — menyu klienti Zod bilan

O'tgan darsdagi 42-api/menu-api.ts ni kurs/mashqlar/15/43-zod/menu-schema.ts ga ko'chiring va parseItem/parseMenu ni Zod sxemasiga almashtiring. MenuItem turi — z.infer. createMenuApi o'zgarmaydi, faqat parseMenu o'rniga Menu.parse. Testlar ham o'sha uchta, faqat narx xatosi endi ZodError. Oxirida qo'lda yozilgan va Zod versiyasining qator sonini solishtiring.

Yechim
ts
// 43-zod/menu-schema.ts — menyu klienti: Zod sxemasi
import { z } from "zod";

export const MenuItem = z.object({
  id: z.number().int().positive(),
  nom: z.string().min(1),
  narx: z.number().nonnegative(),
  turkum: z.enum(["taom", "ichimlik"]),
});
export type MenuItem = z.infer<typeof MenuItem>;
export const Menu = z.array(MenuItem);

export type ApiResponse<T> =
  | { ok: true; data: T }
  | { ok: false; status: number; message: string };

export function createMenuApi(fetchFn: typeof fetch, base: string) {
  return {
    async list(): Promise<ApiResponse<MenuItem[]>> {
      const response = await fetchFn(`${base}/menyu`);
      if (!response.ok) {
        const message = `HTTP ${response.status}`;
        return { ok: false, status: response.status, message };
      }
      return { ok: true, data: Menu.parse(await response.json()) };
    },
  };
}

await response.json() — any, lekin Menu.parse uni unknown deb qabul qiladi va tekshiradi: any bu yerda "oqib ketmaydi". Testlar (43-zod/menu-schema.test.ts) — 42-dagidek, faqat yuqorida import { z } from "zod"; qo'shiladi va bitta test o'zgaradi:

ts
// tekshirilmaydi: menu-schema.test.ts dan parcha
test("narx satr — ZodError", async () => {
  const body = OSH.replace("35000", '"35 000"');
  const api = createMenuApi(fakeFetch(`[${body}]`), BASE);
  await assert.rejects(api.list(), z.ZodError);
});

node --test 43-zod/menu-schema.test.ts — bizda (Node 24.21.0):

text
✔ to'g'ri javob — tekshirilgan menyu (55.9298ms)
✔ narx satr — ZodError (2.7904ms)
✔ 500 — ok: false (1.9597ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 505.4249

Tekshiruv qismi 23 qatordan (parseItem va parseMenu, oradagi bo'sh qator bilan) 8 qatorga (MenuItem, uning turi va Menu) tushdi va turni alohida yozish kerak emas. Narxi — node_modules/zod (npm'dagi o'lchami 6,1 MB, bundlersiz brauzerda 95 modul). Server (Node) uchun bu muammo emas — fayllar diskda. Brauzer uchun esa — «Kutubxona narxi» bo'limidagi raqamlar.

XATOLAR.md ga: TS18048 (safeParse natijasi), ZodError xabarlari (Invalid input: expected number, received string), z.coerce tuzog'i.

bash
git add 15/43-zod 15/package.json 15/package-lock.json 15/XATOLAR.md
git commit -m "15/43: menyu sxemasi — Zod, z.infer, safeParse"

4-mashq: Vazifalar qadami — o'lchov

Bu darsning vazifalar qadami — kod emas, o'lchov. O'tgan darsdagi refactor/api-tekshiruv commit'ining tanasida "Zod o'lchandi" deb yozilgan edi. O'lchovni o'zingiz takrorlang:

  1. vazifalar dan nusxa oling, assets/js/vendor/zod/ ga node_modules/zod ning ESM fayllarini ko'chiring (src, .cjs, .d.ts siz).
  2. serverRoyxatiniTekshir ning Zod versiyasini yozing va dist/ ni build qiling.
  3. DevTools → Network: "Slow 4G" bilan sahifani qayta yuklang. So'rovlar soni, uzatilgan hajm va Load vaqtini yozing. Konsolda Trusted Types xatosi bormi?
Yechim

Bizning o'lchovimizda (Chrome 154, 150 ms kechikish va 1,6 Mbit/s): 97 so'rov (sahifa, ilova va 95 Zod moduli), 233,7 KB, sahifa 3,7 soniyada tayyor. DevTools'dagi "Slow 4G" boshqacha sozlangan — sizning raqamlaringiz farq qiladi, lekin nisbat (o'n baravarga yaqin) shunday qoladi. Vendor nusxa — 136 fayl, 1 058 930 bayt. z.config({ jitless: true }) siz konsolda This document requires 'TrustedScript' assignment xatosi chiqadi.

Xulosa — «Qaror» bo'limida: kanonda qo'lda tekshiruv qoladi, Zod — 16-qismda bundler bilan qayta ko'rib chiqiladi. Bu qadam commit qilinmaydi: o'lchov nusxasini o'chiring (git status toza bo'lsin).

10. Real ishda

  • Zod — TypeScript ekotizimidagi eng mashhur sxema kutubxonasi: Next.js server amallari, tRPC, React Hook Form, Astro kontent kolleksiyalari uni ishlatadi. Ish e'lonlarida "Zod" tez-tez yoziladi.
  • Server tomonda (24-qismdan keyin) — so'rov tanasi, URL parametrlari, .env o'zgaruvchilarini tekshirish: z.object({ PORT: z.coerce.number() }).parse(process.env).
  • Valibot, ArkType — kichik o'lcham muhim bo'lgan brauzer ilovalarida. Standard Schema tufayli ularni almashtirish oson.
  • Intervyu: "TypeScript turlari ish vaqtida tekshiradimi? Unda Zod nima uchun?", "parse va safeParse farqi?", "z.infer qanday ishlaydi?".

Xulosa

  • Sxema kutubxonasi shaklni bir marta yozadi: tekshiruv — Schema.parse, tur — z.infer<typeof Schema>. Ikki haqiqat yo'q.
  • z.infer sehr emas: infer, mapped types va typeof — 20 qatorlik o'z kutubxonamizda ham ishladi.
  • parse — ZodError tashlaydi; safeParse — { success, data | error }, hamma muammolar issues da. z.locales.uz() — o'zbekcha xabarlar.
  • transform, z.coerce, default — ma'lumotni aylantiradi; z.input va z.output farqlanadi.
  • Standard Schema (~standard) — Zod, Valibot va boshqalar uchun umumiy interfeys.
  • Narxi: bundlersiz Zod — 95 modul, 233,7 KB; bundler bilan 92,1 KB, zod/mini 6,1 KB, Valibot 1,7 KB. vazifalar da qo'lda tekshiruv qoldi.

Keyingi dars: Async kod va xatolarni tiplash — Promise<T>, catch (e: unknown) va Result<T, E>: kutilgan xatoni qiymat qilib qaytaramiz.

Manbalar

  • Zod hujjatlari (v4): "Basic usage", "Defining schemas", "Customizing errors", "Codecs/transforms" — zod.dev
  • Standard Schema — standardschema.dev, @standard-schema/spec 1.1.0
  • Valibot hujjatlari — valibot.dev
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Zod va TypeScript: turni sxemadan chiqarish — z.object, z.infer, safeParse — IlmHamroh