20-bo‘lim
Amaliy loyiha
Dokon omborini noldan qurish - turlar, tekshiruv, shartnoma, biznes mantig'i, sinovlar va qurish.
Ushbu bo‘lim mundarijasi
Endi darslikdagi hamma narsani bitta ishlaydigan loyihada birlashtiramiz.
Qurayotgan narsamiz - dokon ombori: mahsulot qo'shadi, tashqi ma'lumotni tekshiradi, sotuvni rasmiylashtiradi va qoldiqni yuritadi.
Har bir qadamda qaysi bob ishlayotganini ko'rsatib boramiz.
1. Loyiha sozlamasi #
{
"name": "dokon",
"type": "module",
"private": true
}
{
"compilerOptions": {
"target": "es2022",
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
echo "sozlama tayyor"
sozlama tayyor
Bu - 12-bobdagi tavsiya etilgan sozlama. "type": "module"
Node.js ga chiqish fayllarini ES moduli sifatida o'qishni
aytadi.
2. Domen turlari #
export type Mahsulot = {
readonly id: number;
nomi: string;
narx: number;
soni: number;
};
export type YangiMahsulot = Omit<Mahsulot, "id">;
export type Tolov =
| { usul: "naqd"; berilgan: number }
| { usul: "karta"; raqam: string };
export type Natija<T> =
| { ok: true; qiymat: T }
| { ok: false; xato: string };
export function yaxshi<T>(qiymat: T): Natija<T> {
return { ok: true, qiymat };
}
export function yomon<T>(xato: string): Natija<T> {
return { ok: false, xato };
}
tsc --noEmit && echo "tsc: xato yo_q"
tsc: xato yo_q
Yigirma qatorda to'rtta bob ishladi:
| Yozuv | Bob |
|---|---|
readonly id | 4 - obyekt turlari |
Omit<Mahsulot, "id"> | 13 - utility turlar |
Tolov ajratilgan unioni | 6 - union |
Natija<T> generigi | 9 - generiklar |
YangiMahsulot - bazaga yozishdan oldingi holat: id ni
baza beradi, shuning uchun uni chiqarib tashladik.
3. Tashqi ma'lumotni tekshirish #
import { yaxshi, yomon } from "./turlar.js";
import type { Natija, YangiMahsulot } from "./turlar.js";
function obyektmi(x: unknown): x is Record<string, unknown> {
return typeof x === "object" && x !== null && !Array.isArray(x);
}
export function tekshir(x: unknown): Natija<YangiMahsulot> {
if (!obyektmi(x)) return yomon("obyekt kutilgan");
const { nomi, narx, soni } = x;
if (typeof nomi !== "string" || nomi.trim() === "") {
return yomon("nomi bosh bolmasligi kerak");
}
if (typeof narx !== "number" || narx <= 0) {
return yomon("narx musbat son bolishi kerak");
}
if (typeof soni !== "number" || !Number.isInteger(soni) || soni < 0) {
return yomon("soni manfiy bolmagan butun son bolishi kerak");
}
return yaxshi({ nomi, narx, soni });
}
tsc --noEmit && echo "tsc: xato yo_q"
tsc: xato yo_q
Bu - loyihaning chegarasi. Bu yerdan keyin hamma narsa turlangan, bu yergacha esa hech narsaga ishonilmaydi.
| Yozuv | Bob |
|---|---|
x: unknown | 15 - hech narsaga ishonmaslik |
x is Record<...> | 7 - tur predikati |
typeof nomi !== "string" | 7 - toraytirish |
import type | 11 - tur importi |
"./turlar.js" | 11 - import yo'li |
nomi.trim() === "" va narx <= 0 - turlar tuta olmaydigan
tekshiruvlar. Tur string deydi, "bo'sh bo'lmagan satr"
demaydi.
4. Tekshiruv sinovlari #
import { test } from "node:test";
import assert from "node:assert/strict";
import { tekshir } from "./tekshir.js";
const holatlar: Array<[unknown, boolean]> = [
[{ nomi: "Olma", narx: 12000, soni: 10 }, true],
[{ nomi: "", narx: 12000, soni: 10 }, false],
[{ nomi: "Olma", narx: -1, soni: 10 }, false],
[{ nomi: "Olma", narx: 12000, soni: 1.5 }, false],
["mahsulot emas", false],
[null, false],
];
for (const [kirish, kutilgan] of holatlar) {
test(`tekshir: ${JSON.stringify(kirish)}`, () => {
assert.equal(tekshir(kirish).ok, kutilgan);
});
}
node --import tsx --test src/tekshir.test.ts 2>&1 \
| grep -E '^(ok|not ok|# (tests|pass|fail))'
ok 1 - tekshir: {"nomi":"Olma","narx":12000,"soni":10}
ok 2 - tekshir: {"nomi":"","narx":12000,"soni":10}
ok 3 - tekshir: {"nomi":"Olma","narx":-1,"soni":10}
ok 4 - tekshir: {"nomi":"Olma","narx":12000,"soni":1.5}
ok 5 - tekshir: "mahsulot emas"
ok 6 - tekshir: null
# tests 6
# pass 6
# fail 0
Array<[unknown, boolean]> - jadvalli sinov (19-bob).
Kirish turi ataylab unknown: biz aynan noto'g'ri
ma'lumotni sinamoqchimiz.
5. Ombor shartnomasi #
import type { Mahsulot, YangiMahsulot } from "./turlar.js";
export type Ombor = {
qoshish: (m: YangiMahsulot) => Promise<Mahsulot>;
topish: (id: number) => Promise<Mahsulot | null>;
hammasi: () => Promise<readonly Mahsulot[]>;
yangilash: (
id: number,
ozgarish: Partial<YangiMahsulot>,
) => Promise<Mahsulot | null>;
};
export function xotiraOmbori(): Ombor {
const yozuvlar = new Map<number, Mahsulot>();
let keyingiId = 1;
return {
qoshish: async (m) => {
const yangi: Mahsulot = { id: keyingiId, ...m };
keyingiId += 1;
yozuvlar.set(yangi.id, yangi);
return yangi;
},
topish: async (id) => yozuvlar.get(id) ?? null,
hammasi: async () => [...yozuvlar.values()],
yangilash: async (id, ozgarish) => {
const bor = yozuvlar.get(id);
if (bor === undefined) return null;
const yangi: Mahsulot = { ...bor, ...ozgarish };
yozuvlar.set(id, yangi);
return yangi;
},
};
}
tsc --noEmit && echo "tsc: xato yo_q"
tsc: xato yo_q
Bu yerda ham bir nechta bob birga ishladi:
| Yozuv | Bob |
|---|---|
qoshish: (m) => ... xossa yozuvi | 12 - metod emas, xossa |
Partial<YangiMahsulot> | 13 - utility tur |
Mahsulot | null | 6 - union |
readonly Mahsulot[] | 4 - o'zgarmas massiv |
Map<number, Mahsulot> | 9 - generik |
?? null | 6 - nullish birlashtirish |
async (m) => ... da m turlanmagan | 5 - kontekstli turlash |
Xossa yozuvi ataylab tanlangan: 12-bobda ko'rganimizdek, metod yozuvi parametrlarni ikki tomonlama tekshiradi va teshik qoldiradi.
6. Biznes mantig'i #
import { yaxshi, yomon } from "./turlar.js";
import type { Mahsulot, Natija, Tolov } from "./turlar.js";
import type { Ombor } from "./ombor.js";
import { tekshir } from "./tekshir.js";
export async function qoshish(
ombor: Ombor,
xom: unknown,
): Promise<Natija<Mahsulot>> {
const t = tekshir(xom);
if (!t.ok) return yomon(t.xato);
return yaxshi(await ombor.qoshish(t.qiymat));
}
function tolovni(tolov: Tolov, jami: number): Natija<string> {
switch (tolov.usul) {
case "naqd":
if (tolov.berilgan < jami) {
return yomon(`kam berildi: ${tolov.berilgan} < ${jami}`);
}
return yaxshi(`naqd, qaytim ${tolov.berilgan - jami}`);
case "karta":
return yaxshi(`karta ${tolov.raqam}`);
default: {
const qolgan: never = tolov;
return qolgan;
}
}
}
export async function sotish(
ombor: Ombor,
id: number,
soni: number,
tolov: Tolov,
): Promise<Natija<string>> {
const m = await ombor.topish(id);
if (m === null) return yomon(`mahsulot topilmadi: ${id}`);
if (m.soni < soni) return yomon(`omborda yetarli emas: ${m.soni}`);
const jami = m.narx * soni;
const t = tolovni(tolov, jami);
if (!t.ok) return t;
await ombor.yangilash(id, { soni: m.soni - soni });
return yaxshi(`${m.nomi} x${soni} = ${jami}, ${t.qiymat}`);
}
tsc --noEmit && echo "tsc: xato yo_q"
tsc: xato yo_q
Diqqat qilinadigan uchta joy:
if (!t.ok) return yomon(t.xato);- shundan keyint.qiymatochiladi (6-bob, ajratilgan union);const qolgan: never = tolov;-Tolovga uchinchi usul qo'shilsa, bu qator darhol xato beradi (6-bob);ombor: Ombor- haqiqiy baza emas, shartnoma qabul qilinadi. Shu tufayli sinov yozish oson (19-bob).
7. Xizmat sinovlari #
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { xotiraOmbori } from "./ombor.js";
import { qoshish, sotish } from "./xizmat.js";
import type { Tolov } from "./turlar.js";
const naqd: Tolov = { usul: "naqd", berilgan: 40000 };
const karta: Tolov = { usul: "karta", raqam: "8600" };
describe("sotish", () => {
it("naqd tolovda qaytim hisoblanadi", async () => {
const o = xotiraOmbori();
await qoshish(o, { nomi: "Olma", narx: 12000, soni: 10 });
const r = await sotish(o, 1, 3, naqd);
assert.equal(r.ok, true);
assert.equal(
r.ok ? r.qiymat : "",
"Olma x3 = 36000, naqd, qaytim 4000",
);
});
it("sotuvdan keyin soni kamayadi", async () => {
const o = xotiraOmbori();
await qoshish(o, { nomi: "Olma", narx: 12000, soni: 10 });
await sotish(o, 1, 3, karta);
const m = await o.topish(1);
assert.equal(m?.soni, 7);
});
it("yetarli bolmasa rad etadi", async () => {
const o = xotiraOmbori();
await qoshish(o, { nomi: "Olma", narx: 12000, soni: 2 });
const r = await sotish(o, 1, 5, karta);
assert.equal(r.ok, false);
assert.equal(r.ok ? "" : r.xato, "omborda yetarli emas: 2");
});
it("kam pul berilsa rad etadi", async () => {
const o = xotiraOmbori();
await qoshish(o, { nomi: "Anor", narx: 25000, soni: 5 });
const r = await sotish(o, 1, 2, naqd);
assert.equal(r.ok ? "" : r.xato, "kam berildi: 40000 < 50000");
});
it("mahsulot yoq bolsa rad etadi", async () => {
const o = xotiraOmbori();
const r = await sotish(o, 99, 1, karta);
assert.equal(r.ok ? "" : r.xato, "mahsulot topilmadi: 99");
});
});
node --import tsx --test src/xizmat.test.ts 2>&1 \
| grep -E '^(ok|not ok|# (tests|pass|fail))'
ok 1 - sotish
# tests 5
# pass 5
# fail 0
const naqd: Tolov = {...} - annotatsiya ataylab yozilgan.
Usiz usul string ga kengayardi (14-bob) va Tolov ga mos
kelmasdi.
r.ok ? r.qiymat : "" - r.qiymat ga faqat ok tekshirilgach
kirish mumkin.
8. Tur sinovlari #
import { tekshir } from "./tekshir.js";
import type { Mahsulot, YangiMahsulot } from "./turlar.js";
const a: YangiMahsulot = { nomi: "Olma", narx: 12000, soni: 1 };
// @ts-expect-error - YangiMahsulot da id yoq
const b: YangiMahsulot = { id: 1, nomi: "Olma", narx: 1, soni: 1 };
declare const m: Mahsulot;
// @ts-expect-error - id readonly
m.id = 2;
const r = tekshir({});
// @ts-expect-error - avval ok tekshirilishi kerak
r.qiymat;
console.log(a.nomi);
export {};
tsc --noEmit && echo "tur sinovi otdi"
tur sinovi otdi
Uchta va'da yozib qo'yildi:
YangiMahsulotdaidbo'lmasligi kerak;Mahsulot.ido'zgartirilmasligi kerak;Natijani tekshirmasdan ishlatib bo'lmasligi kerak.
Kimdir readonly ni olib tashlasa yoki Omit ni buzsa, bu
fayl TS2578 beradi (19-bob).
9. Dasturni ishga tushirish #
import { xotiraOmbori } from "./ombor.js";
import { qoshish, sotish } from "./xizmat.js";
import type { Tolov } from "./turlar.js";
const ombor = xotiraOmbori();
const kirish: unknown[] = [
{ nomi: "Olma", narx: 12000, soni: 10 },
{ nomi: "Anor", narx: 25000, soni: 4 },
{ nomi: "", narx: 5000, soni: 1 },
{ nomi: "Uzum", narx: -1, soni: 1 },
"mahsulot emas",
];
for (const x of kirish) {
const r = await qoshish(ombor, x);
console.log(r.ok ? `+ #${r.qiymat.id} ${r.qiymat.nomi}` : `- ${r.xato}`);
}
console.log("---");
const sotuvlar: Array<{ id: number; soni: number; tolov: Tolov }> = [
{ id: 1, soni: 3, tolov: { usul: "naqd", berilgan: 40000 } },
{ id: 2, soni: 2, tolov: { usul: "karta", raqam: "8600" } },
{ id: 1, soni: 99, tolov: { usul: "karta", raqam: "8600" } },
];
for (const s of sotuvlar) {
const r = await sotish(ombor, s.id, s.soni, s.tolov);
console.log(r.ok ? `+ ${r.qiymat}` : `- ${r.xato}`);
}
console.log("---");
for (const m of await ombor.hammasi()) {
console.log(`#${m.id} ${m.nomi}: ${m.soni} dona`);
}
export {};
tsx src/asosiy.ts
+ #1 Olma
+ #2 Anor
- nomi bosh bolmasligi kerak
- narx musbat son bolishi kerak
- obyekt kutilgan
---
+ Olma x3 = 36000, naqd, qaytim 4000
+ Anor x2 = 50000, karta 8600
- omborda yetarli emas: 7
---
#1 Olma: 7 dona
#2 Anor: 2 dona
Beshta kirishdan ikkitasi qabul qilindi, uchtasi aniq sabab bilan rad etildi. Hech qayerda istisno tashlanmadi.
kirish: unknown[] va sotuvlar: Array<{...}> - ikkalasida
ham annotatsiya bor. Birinchisi ishonchsizlikni, ikkinchisi
esa kengayishdan himoyani bildiradi.
10. To'liq sinov va qurish #
node --import tsx --test "src/*.test.ts" 2>&1 \
| grep -E '^(ok|not ok|# (tests|pass|fail))'
echo "--- qurish ---"
tsc
ls dist
echo "--- qurilgan dasturni ishga tushirish ---"
node dist/asosiy.js | tail -4
ok 1 - tekshir: {"nomi":"Olma","narx":12000,"soni":10}
ok 2 - tekshir: {"nomi":"","narx":12000,"soni":10}
ok 3 - tekshir: {"nomi":"Olma","narx":-1,"soni":10}
ok 4 - tekshir: {"nomi":"Olma","narx":12000,"soni":1.5}
ok 5 - tekshir: "mahsulot emas"
ok 6 - tekshir: null
ok 7 - sotish
# tests 11
# pass 11
# fail 0
--- qurish ---
asosiy.js
ombor.js
tekshir.js
tekshir.test.js
turlar.js
turlar.tur-sinov.js
xizmat.js
xizmat.test.js
--- qurilgan dasturni ishga tushirish ---
- omborda yetarli emas: 7
---
#1 Olma: 7 dona
#2 Anor: 2 dona
Qurilgan JavaScript tsx siz, oddiy node bilan ishladi.
dist/ ga tushib qolganls dist da *.test.js fayllari ham ko'rinib turibdi -
chunki include: ["src"] hammasini qamrab oldi.
Haqiqiy loyihada qurish uchun alohida sozlama yoziladi:
// tsconfig.build.json
{
"extends": "./tsconfig.json",
"exclude": ["src/**/*.test.ts", "src/**/*.tur-sinov.ts"]
}
Keyin tsc -p tsconfig.build.json. Tahrirlovchi esa asosiy
tsconfig.json ni ishlatadi va sinovlarni ham tekshiradi.
11. Qat'iy sozlama ish beradi #
Loyihaga bitta yangi fayl qo'shib ko'ramiz:
import { xotiraOmbori } from "./ombor.js";
const ombor = xotiraOmbori();
const hammasi = await ombor.hammasi();
const birinchi = hammasi[0];
console.log(birinchi.nomi);
export {};
tsc --noEmit 2>&1 | fold -sw 74
rm -f src/xato.ts
src/xato.ts(7,13): error TS18048: 'birinchi' is possibly 'undefined'.
Ombor bo'sh bo'lishi mumkin, demak hammasi[0] undefined
bo'lishi mumkin. Buni noUncheckedIndexedAccess tutdi -
12-bobda ko'rganimizdek, u strict ga kirmaydi.
Bu xato bo'lmasa, dastur bo'sh omborda yiqilardi.
tsc --noEmit && echo "tsc: xato yo_q"
ls src
tsc: xato yo_q
asosiy.ts
ombor.ts
tekshir.test.ts
tekshir.ts
turlar.ts
turlar.tur-sinov.ts
xizmat.test.ts
xizmat.ts
Sakkizta fayl, any yo'q, as yo'q (bitta tur predikatidan
tashqari), 11 ta sinov va to'liq qat'iy sozlama.
Butun darslikning xaritasi #
| Bob | Asosiy fikr |
|---|---|
| 1 | Turlar kompilyatsiyadan keyin yo'qoladi |
| 2 | tsconfig.json bo'lmasa, bayroqlar e'tiborsiz qoladi |
| 3 | const literal, let kengaygan tur beradi |
| 4 | Ortiqcha maydon faqat literalda tekshiriladi |
| 5 | void ga berish mumkin, undan olish mumkin emas |
| 6 | Union maydonlarni kamaytiradi, & ko'paytiradi |
| 7 | Predikat, ! va o'zgarish - uchta teshik |
| 8 | interface birlashadi, type birlashmaydi |
| 9 | Generik kirish turini chiqishga uzatadi |
| 10 | private ish vaqtida yo'q, # bor |
| 11 | Modulsiz fayl global sohada yashaydi |
| 12 | Metod yozuvi strict da ham teshik qoldiradi |
| 13 | Omit kalitni tekshirmaydi, Pick tekshiradi |
| 14 | satisfies tekshiradi, turni almashtirmaydi |
| 15 | any tarqaladi, unknown to'xtatadi |
| 16 | declare module - tekshirilmaydigan va'da |
| 17 | forEach(async ...) kutmaydi |
| 18 | Ko'chirish bosqichma-bosqich, barglardan boshlab |
| 19 | Turlar va sinovlar turli xatolarni tutadi |
| 20 | Chegarani tor qiling, ichkarida ishoning |
| Mavzu | Nima beradi |
|---|---|
| Zod yoki Valibot | Chegarani qo'lda yozmaslik |
typescript-eslint | no-floating-promises va boshqalar |
Shartli turlar, infer | Murakkab utility turlar |
| Loyiha havolalari (project references) | Katta monorepo |
| Freymvork turlari (React, Express) | Amaliy qo'llash |
Eng foydalisi esa - o'z loyihangizdagi any larni birma-bir
yo'qotish. Har bir yo'qotilgan any - kompilyator uchun
ochilgan yangi hudud.
Xulosa #
- Loyiha qat'iy
tsconfig.jsondan boshlanadi - keyinchalik qat'iylashtirish ancha qiyin. - Domen turlari bitta faylda yig'iladi va qolgan hamma joy ulardan foydalanadi.
Omit<Mahsulot, "id">- "hali saqlanmagan yozuv" ni ifodalashning tabiiy yo'li.Natija<T>istisno o'rniga xatoni turda ko'rsatadi va chaqiruvchini uni ishlashga majbur qiladi.- Tashqi ma'lumot faqat bitta joydan - chegaradan o'tadi;
u yerda tur
unknown. - Turlar tuta olmaydigan qoidalar (bo'sh satr, manfiy narx) ham o'sha chegarada tekshiriladi.
- Ombor shartnoma sifatida beriladi, shuning uchun sinov uchun baza kerak emas.
nevertekshiruvi unionga yangi a'zo qo'shilganda unutilgan joylarni topib beradi.- Tur sinovlari (
@ts-expect-error) turlarning o'zi buzilmasligini kafolatlaydi. noUncheckedIndexedAccessmassivning bo'sh bo'lishi mumkin bo'lgan joyini topdi - ustrictga kirmaydi.- Qurilgan JavaScript hech qanday qo'shimcha vositasiz,
oddiy
nodebilan ishlaydi. - Chegara qancha tor bo'lsa, ichkarida shuncha kam tekshiruv kerak bo'ladi.
- Loyihani noldan qayta yozing va har bir bosqichda
tsc --noEmitni ishga tushiring. Tolovga{ usul: "payme"; telefon: string }qo'shing va kompilyator qaysi qatorlarni ko'rsatishini sanang.tekshirgasoniuchun yuqori chegara qo'shing va sinov yozing.Omborgaochirish(id: number): Promise<boolean>metodini qo'shing.- Soxta omborni
satisfies Omborbilan yozib, sinovlarni undan foydalanadigan qiling. Mahsulotdanreadonlyni olib tashlang va tur sinovi yiqilishini ko'ring.noUncheckedIndexedAccessni o'chirib, 11-qadamdagi xatoning yo'qolishini tasdiqlang.tsconfig.build.jsonyozib, sinovlarnidist/dan chiqarib tashlang.- Xotira ombori o'rniga JSON faylga yozadigan omborni
yozing -
Omborturi o'zgarmasligi kerak. - Butun loyihani
strictdan tashqari barcha qo'shimcha bayroqlar bilan qurib ko'ring va nechta yangi xato chiqqanini yozib qo'ying.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.