Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. Ikki yondashuv: imperativ va deklarativ
- 2.1 Taksi misolida
- 2.2 DOM'da
- 3. Holat (state) — yagona haqiqat manbai
- 3.1 Holat obyekti
- 3.2 Nima holatga kiradi, nima kirmaydi
- 4. render() — holatdan ekranga
- 4.1 Funksiya
- 4.2 Holatni o'zgartiruvchi funksiyalar
- 5. Tugmalar bilan: to'liq ilova
- 5.1 Bosish — holat — render
- 5.2 Filtr — holatning bir qismi
- 6. "Hammasini qayta chizish"ning narxi
- 6.1 Fokus yo'qoladi
- 6.2 Holatda bo'lmagan narsa yo'qoladi
- 6.3 Katta ro'yxatda tezlik
- 7. Qolip qoidalari
- 8. Ko'p uchraydigan xatolar
- 8.1 render() ni chaqirishni unutish
- 8.2 DOM'ni to'g'ridan-to'g'ri o'zgartirish
- 8.3 id o'rniga indeks
- 8.4 Hosila ma'lumotni holatda saqlash
- 9. Mashqlar
- 1-mashq (oson): Buyurtma savati
- 2-mashq (o'rta): Bo'sh qatorlar va "Savat bo'sh"
- 3-mashq (qiyin): Bajarilganlarni tozalash
- 4-mashq: Portfolio qadami — loyihalar filtri holatdan
- 10. Real ishda
- Xulosa
- Manbalar
Holatdan UI chizish: state va render() — ma'lumot bitta joyda, ekran undan
Qisqacha: Ilova ma'lumotini DOM'da emas, bitta JavaScript obyektida — holatda (state) saqlang. Ekranni esa bitta
render()funksiyasi shu holatdan har safar to'liq qayta chizsin. Har o'zgarish ikki qadam: holatni o'zgartirish, keyinrender(). Shunda ekran va ma'lumot hech qachon bir-biridan ajralib qolmaydi — React kabi freymvorklar ham aynan shu g'oyaga qurilgan.
Bu darsda
- Qo'lda DOM'ni yangilash nega ertami-kechmi xatoga olib kelishini tushuntira olasiz.
- Ilova ma'lumotini bitta holat obyektida saqlay olasiz.
- Holatdan butun ro'yxatni chizadigan
render()funksiyasini yoza olasiz. - Holatni o'zgartiradigan funksiyalar va filtrni
for...ofbilan qo'sha olasiz. - Imperativ va deklarativ yondashuv farqini va "hammasini qayta chizish"ning narxini bilasiz.
Oldin bilishingiz kerak: Ko'p elementni samarali chizish, Element o'lchamlari va scroll, Obyekt: kalit-qiymat juftliklari, Massivda qidirish va tekshirish, Pure funksiya va side effect.
1. Nega bu kerak?
Jasur aka «Bahor» xodimlari uchun "Kunlik ishlar" sahifasini xohlaydi: ishlar ro'yxati, har ish yonida "Bajarildi" tugmasi va pastda hisob — "3 ta ishdan 1 tasi bajarildi". Oddiy ko'rinadi.
Hozirgacha o'rgangan usulimiz bilan yozamiz: ish qo'shilsa — li yaratamiz va hisobni yangilaymiz. Ish o'chirilsa — li ni olib tashlaymiz va... hisobni yangilashni unutamiz:
<ul id="ishlar">
<li>Go'sht olish</li>
<li>Choynaklarni yuvish</li>
</ul>
<p id="hisob">2 ta ish</p>
<script>
const ishlar = document.getElementById("ishlar");
const hisob = document.getElementById("hisob");
let soni = 2;
function ishQosh(matn) {
const li = document.createElement("li");
li.textContent = matn;
ishlar.append(li);
soni++;
hisob.textContent = `${soni} ta ish`;
}
function ishniOchir(li) {
li.remove();
// hisobni yangilash esdan chiqdi
}
ishQosh("Gilam qoqish");
ishniOchir(ishlar.firstElementChild);
console.log(hisob.textContent);
console.log(`Ro'yxatda: ${ishlar.children.length} ta`);
</script>Konsolda:
3 ta ish
Ro'yxatda: 2 taEkranda ikkita ish, ostida esa "3 ta ish". Xato xabari yo'q — sahifa shunchaki yolg'on gapiryapti.
Nega bunday bo'ldi? Bitta haqiqat — "nechta ish bor" — uch joyda yashaydi: soni o'zgaruvchisida, li lar sonida va hisob matnida. Har funksiya uchalasini qo'lda bir xil holatda ushlab turishi kerak. Ikki funksiyada buni eslab qolish mumkin. Lekin "Bajarildi", "Tahrirlash", "Filtr", "Hammasini tozalash" qo'shilsa — har yangi funksiya har eski joyni bilishi kerak bo'ladi. Ertami-kechmi biri unutiladi.
Bu dars shu muammoning nomini aytadi va uning yechimini — butun zamonaviy frontendning poydevorini — ko'rsatadi.
2. Ikki yondashuv: imperativ va deklarativ
2.1 Taksi misolida
Yandex Go'da taksiga chiqdingiz. Haydovchiga ikki xil yo'l ko'rsatish mumkin:
- "To'g'riga yuring. Svetoforda chapga. Ikkinchi ko'chada o'ngga. Shu yerda to'xtang." Siz har qadamni aytasiz.
- "Chilonzor, 9-kvartal." Siz faqat natijani aytasiz, yo'lni navigator o'zi topadi.
Birinchisi — imperativ ("buyruq beruvchi") yondashuv: nima qilish kerakligini qadamma-qadam aytasiz. Ikkinchisi — deklarativ ("e'lon qiluvchi"): qanday natija kerakligini aytasiz, qanday erishishni esa tizim hal qiladi.
Siz deklarativ yozuvni allaqachon bilasiz: HTML va CSS. color: green yozganingizda, brauzerga "har pikselni yashil bo'ya" demaysiz — natijani aytasiz.
2.2 DOM'da
| Imperativ | Deklarativ | |
|---|---|---|
| Fikr | "Bu li ni o'chir, hisobni 2 qil" |
"Ro'yxat holatdagi ishlarga teng bo'lsin" |
| Har o'zgarishda | Qaysi joylarni o'zgartirishni o'ylaysiz | Holatni o'zgartirib, hammasini qayta chizasiz |
| Xato manbai | Biror joy unutiladi | Joy unutilmaydi — chizish bitta |
«Nega bu kerak?» bo'limidagi kod — toza imperativ. Endi uni deklarativ uslubga o'tkazamiz. JavaScript'ning o'zi deklarativ bo'lmaydi — lekin biz kodni shunday tuzamizki, ekran doim bitta qoidadan chiqadi.
3. Holat (state) — yagona haqiqat manbai
3.1 Holat obyekti
Holat (state) — ilova hozir "biladigan" hamma ma'lumot, bitta joyda. Bizning ilovada bu — ishlar ro'yxati:
const holat = {
ishlar: [
{ id: 1, matn: "Go'sht olish", bajarildi: true },
{ id: 2, matn: "Choynaklarni yuvish", bajarildi: false },
{ id: 3, matn: "Gilam qoqish", bajarildi: false },
],
keyingiId: 4,
};Bu — oddiy obyekt, ichida obyektlar massivi. Yangi narsa sintaksisda emas, qoidada: ishlar haqidagi har qanday savolga javob shu obyektdan olinadi. DOM'dan emas.
Hayotdan o'xshatish: «Bahor»dagi buyurtma daftari. Zaldagi doskada ham buyurtmalar yozilgan, lekin u daftardan ko'chirilgan. Doska bilan daftar farq qilsa — daftar haq. Doskani o'chirib, daftardan qayta ko'chirasiz. Bu tamoyilning nomi — yagona haqiqat manbai (single source of truth).
3.2 Nima holatga kiradi, nima kirmaydi
Holatga uch qoida:
- Faqat ma'lumot. Satr, son, mantiqiy qiymat, massiv, obyekt. DOM elementlari (
li,button) holatga kirmaydi — ular holatdan yasaladi. - Hosila ma'lumot saqlanmaydi. "Nechta ish bajarilgan" — holatda yo'q, chunki uni
ishlardan har doim sanab olish mumkin. Saqlasangiz, u yana bir "uchinchi joy" bo'ladi va «Nega bu kerak?» bo'limidagi xato qaytadi. - Har ishning
idsi bor. Ishlar tartibi o'zgarishi, o'chishi mumkin — indeks (0, 1, 2) siljiydi,idesa o'zgarmaydi. "2-raqamli ish" deganda doim bitta ish tushuniladi.keyingiId— yangi ishga beriladigan navbatdagi raqam.
Tekshirib ko'ring: Holatga
bajarilganlarSoni: 1maydonini qo'shsak nima yomon?
Javob
Har ish bajarilganda yoki qaytarilganda ikki joyni o'zgartirish kerak bo'ladi: ishning bajarildi maydonini va bajarilganlarSoni ni. Bittasi unutilsa — ular bir-biriga zid bo'ladi. Sonni render() ichida sanab olish arzon va har doim to'g'ri.
4. render() — holatdan ekranga
4.1 Funksiya
Chizish (render) — holatdan ekran ko'rinishini yasash. Bizda bu bitta funksiya: u holatni o'qiydi va ro'yxat bilan hisobni boshidan quradi. Ko'p elementni samarali chizish darsidagi qolip — fragment va replaceChildren — aynan shu ish uchun:
<style>
.bajarildi { text-decoration: line-through; color: #5a6b62; }
</style>
<ul id="ishlar"></ul>
<p id="hisob"></p>
<script>
const holat = {
ishlar: [
{ id: 1, matn: "Go'sht olish", bajarildi: true },
{ id: 2, matn: "Choynaklarni yuvish", bajarildi: false },
{ id: 3, matn: "Gilam qoqish", bajarildi: false },
],
keyingiId: 4,
};
const royxat = document.getElementById("ishlar");
const hisob = document.getElementById("hisob");
function render() {
const quti = document.createDocumentFragment();
let bajarilgan = 0;
for (const ish of holat.ishlar) {
const li = document.createElement("li");
li.textContent = ish.matn;
if (ish.bajarildi) {
li.classList.add("bajarildi");
bajarilgan++;
}
quti.append(li);
}
royxat.replaceChildren(quti);
const jami = holat.ishlar.length;
hisob.textContent =
`${jami} ta ishdan ${bajarilgan} tasi bajarildi`;
}
render();
console.log(royxat.children);
console.log(hisob.textContent);
</script>Konsolda:
HTMLCollection(3) [li.bajarildi, li, li]
3 ta ishdan 1 tasi bajarildiHTML'da ul bo'sh edi — hamma narsani render() chizdi. Birinchi ish ustidan chizilgan: bajarildi class'i holatdan keldi.
render() ning uch muhim xususiyati:
- Hammasini chizadi, "nima o'zgardi"ni so'ramaydi.
replaceChildreneskisini tozalaydi — o'zgargan qatorni qidirish shart emas. - Faqat o'qiydi.
render()holatni o'zgartirmaydi. U ekranni o'zgartiradi — bu uning yagona yon ta'siri (side effect) (Pure funksiya va side effect). - Necha marta chaqirsangiz ham, natija bir xil. Holat o'zgarmagan bo'lsa, ikkinchi
render()aynan o'sha ekranni chizadi. Bu xususiyat xotirjamlik beradi: "ortiqcha" render xato emas.
Matn textContent bilan qo'yildi, innerHTML bilan emas. Ishlar matnini xodimlar yozadi — XSS darsidagi qoida bu yerda ham amal qiladi.
4.2 Holatni o'zgartiruvchi funksiyalar
Endi har harakat uchun funksiya yozamiz. Hammasi bir xil qolipda: holatni o'zgartir → render(). DOM'ga ular tegmaydi:
<style>
.bajarildi { text-decoration: line-through; color: #5a6b62; }
</style>
<ul id="ishlar"></ul>
<p id="hisob"></p>
<script>
const holat = {
ishlar: [
{ id: 1, matn: "Go'sht olish", bajarildi: true },
{ id: 2, matn: "Choynaklarni yuvish", bajarildi: false },
],
keyingiId: 3,
};
const royxat = document.getElementById("ishlar");
const hisob = document.getElementById("hisob");
function render() {
const quti = document.createDocumentFragment();
let bajarilgan = 0;
for (const ish of holat.ishlar) {
const li = document.createElement("li");
li.textContent = ish.matn;
if (ish.bajarildi) {
li.classList.add("bajarildi");
bajarilgan++;
}
quti.append(li);
}
royxat.replaceChildren(quti);
const jami = holat.ishlar.length;
hisob.textContent =
`${jami} ta ishdan ${bajarilgan} tasi bajarildi`;
console.log(hisob.textContent);
}
function ishQosh(matn) {
holat.ishlar.push({
id: holat.keyingiId,
matn,
bajarildi: false,
});
holat.keyingiId++;
render();
}
function ishniAlmashtir(id) {
const ish = holat.ishlar.find((i) => i.id === id);
ish.bajarildi = !ish.bajarildi;
render();
}
function ishniOchir(id) {
const orin = holat.ishlar.findIndex((i) => i.id === id);
holat.ishlar.splice(orin, 1);
render();
}
render();
ishQosh("Gilam qoqish");
ishniAlmashtir(2);
ishniOchir(1);
</script>Konsolda:
2 ta ishdan 1 tasi bajarildi
3 ta ishdan 1 tasi bajarildi
3 ta ishdan 2 tasi bajarildi
2 ta ishdan 1 tasi bajarildiTo'rt qator — to'rt render(). Har harakatdan keyin hisob o'z-o'zidan to'g'ri: o'chirishda hisobni yangilashni "unutish" endi mumkin emas, chunki hisobni hech kim qo'lda yangilamaydi.
Funksiyalar ichidagi asboblar 08-qismdan tanish: push va splice — Element qo'shish va olib tashlash, find va findIndex — Massivda qidirish. { id: holat.keyingiId, matn, bajarildi: false } dagi yolg'iz matn — qisqa yozuv (shorthand): matn: matn bilan bir xil.
Butun sikl diagrammada:
flowchart LR
H["Harakat: tugma"] --> F["ishQosh, ishniAlmashtir"]
F --> S[("holat")]
S --> R["render()"]
R --> D["DOM: ekran"]
D -.->|"foydalanuvchi ko'radi"| HStrelkalar bir tomonga aylanadi: harakat → holat → render → ekran. Ekrandan holatga to'g'ridan-to'g'ri yo'l yo'q. Bu — qolipning eng muhim qoidasi.
Tekshirib ko'ring:
ishniOchir(1)dan keyinholat.keyingiIdnechaga teng? Yangi ish qo'shilsa, unga qaysiidberiladi?
Javob
keyingiId — 4: ishQosh uni 3 dan 4 ga oshirgan, o'chirish esa unga tegmaydi. Yangi ish id: 4 oladi. O'chirilgan ishning id si (1) qayta ishlatilmaydi — bu yaxshi: eski id ga murojaat qilgan kod hech qachon boshqa ishni o'zgartirib qo'ymaydi.
5. Tugmalar bilan: to'liq ilova
5.1 Bosish — holat — render
Endi har ish yoniga tugma, tepaga esa yangi ish qo'shish maydoni qo'yamiz. Tugmalar uchun yana addEventListener qolipi: tugma.addEventListener("click", () => ...) — "bosilganda shu funksiyani chaqir". Uni keyingi darsda to'liq o'rganamiz.
Maydon matnini value xususiyati beradi (Atributlar va xususiyatlar). Formalarni alohida darslarda o'rganamiz (Forma qiymatlarini o'qish), shuning uchun bu yerda oddiy maydon va tugma — formasiz.
<style>
#ishlar li { margin-block: 4px; }
.bajarildi span { text-decoration: line-through; color: #5a6b62; }
</style>
<p>
<input id="yangi" aria-label="Yangi ish">
<button type="button" id="qosh">Qo'shish</button>
</p>
<ul id="ishlar"></ul>
<p id="hisob"></p>
<script>
const holat = {
ishlar: [
{ id: 1, matn: "Go'sht olish", bajarildi: true },
{ id: 2, matn: "Choynaklarni yuvish", bajarildi: false },
],
keyingiId: 3,
};
const royxat = document.getElementById("ishlar");
const hisob = document.getElementById("hisob");
const maydon = document.getElementById("yangi");
function render() {
const quti = document.createDocumentFragment();
let bajarilgan = 0;
for (const ish of holat.ishlar) {
const li = document.createElement("li");
const matn = document.createElement("span");
matn.textContent = ish.matn;
const tugma = document.createElement("button");
tugma.type = "button";
tugma.textContent = ish.bajarildi ? "Qaytarish" : "Bajarildi";
tugma.addEventListener("click", () => ishniAlmashtir(ish.id));
if (ish.bajarildi) {
li.classList.add("bajarildi");
bajarilgan++;
}
li.append(matn, " ", tugma);
quti.append(li);
}
royxat.replaceChildren(quti);
const jami = holat.ishlar.length;
hisob.textContent =
`${jami} ta ishdan ${bajarilgan} tasi bajarildi`;
}
function ishQosh(matn) {
holat.ishlar.push({
id: holat.keyingiId,
matn,
bajarildi: false,
});
holat.keyingiId++;
render();
}
function ishniAlmashtir(id) {
const ish = holat.ishlar.find((i) => i.id === id);
ish.bajarildi = !ish.bajarildi;
render();
console.log(`${ish.matn}: ${ish.bajarildi}`);
}
document.getElementById("qosh").addEventListener("click", () => {
const matn = maydon.value.trim();
if (matn === "") {
return;
}
ishQosh(matn);
maydon.value = "";
});
render();
</script>Sinab ko'ring:
- "Choynaklarni yuvish" yonidagi Bajarildi ni bosing — matn ustidan chiziladi, tugma "Qaytarish" ga aylanadi, hisob "2 ta ishdan 2 tasi bajarildi" bo'ladi, konsolda
Choynaklarni yuvish: true. - Maydonga "Non olish" deb yozib, Qo'shish ni bosing — ro'yxat oxirida yangi ish, hisob "3 ta ishdan ...". Maydon tozalanadi.
- Bo'sh maydon bilan Qo'shish — hech narsa bo'lmaydi:
trimdan keyin bo'sh satr (Matnni tozalash) va erta qaytish.
Bitta qiziq detal. Sikl ichidagi () => ishniAlmashtir(ish.id) — har tugma o'z ishining id sini eslab qoladi. Ikkinchi aylanishda yaratilgan tugma "2" ni eslaydi, birinchisi — "1" ni. Funksiyaning o'zi yaratilgan joydagi o'zgaruvchilarni "eslab qolishi" — closure deyiladi. Uni 10-qismda chuqur o'rganamiz, hozir faqat natijasiga ishoning: har tugma o'z ishini o'zgartiradi.
Yana bir savol tug'ilishi mumkin: render() har safar tugmalarga tinglovchi qo'shsa, ular ko'payib ketmaydimi? Yo'q. replaceChildren eski tugmalarni ularning tinglovchilari bilan birga olib tashlaydi, yangi tugmada esa bittagina tinglovchi bo'ladi. Bir bosish — konsolda bitta qator, buni o'zingiz ko'rasiz.
Tekshirib ko'ring: Kodda "Bajarildi" tugmasi bosilganda
lining class'ini o'zgartiradigan biror qator bormi?
Javob
Yo'q. Tugma faqat ishniAlmashtir ni chaqiradi — u holatdagi bajarildi ni teskarisiga o'zgartiradi va render() ni chaqiradi. Class'ni render() qo'yadi, chunki u holatda bajarildi: true ni ko'rdi. Tugma ekranni emas, ma'lumotni o'zgartiradi.
5.2 Filtr — holatning bir qismi
Jasur aka: "Faqat qolgan ishlarni ko'rsatadigan tugma bo'lsin". Qaysi filtr tanlangani — bu ham holat, faqat ma'lumot emas, UI holati. Holatga bitta maydon qo'shamiz: filtr: "hammasi". Ekranda qaysi ishlar ko'rinishini esa render() hal qiladi.
Ro'yxatdan shartga mosini tanlash uchun massivning maxsus filter metodi bor, uni 10-qismda o'rganamiz. Hozircha o'zimiz bilgan yo'l — for...of va if — bemalol yetadi:
<ul id="ishlar"></ul>
<script>
const holat = {
ishlar: [
{ id: 1, matn: "Go'sht olish", bajarildi: true },
{ id: 2, matn: "Choynaklarni yuvish", bajarildi: false },
{ id: 3, matn: "Gilam qoqish", bajarildi: false },
],
filtr: "hammasi",
};
const royxat = document.getElementById("ishlar");
function mosmi(ish) {
if (holat.filtr === "qolgan") {
return !ish.bajarildi;
}
if (holat.filtr === "bajarilgan") {
return ish.bajarildi;
}
return true;
}
function korinadiganIshlar() {
const natija = [];
for (const ish of holat.ishlar) {
if (mosmi(ish)) {
natija.push(ish);
}
}
return natija;
}
function render() {
const quti = document.createDocumentFragment();
for (const ish of korinadiganIshlar()) {
const li = document.createElement("li");
li.textContent = ish.matn;
quti.append(li);
}
royxat.replaceChildren(quti);
console.log(`${holat.filtr}: ${royxat.children.length} ta`);
}
function filtrniTanla(filtr) {
holat.filtr = filtr;
render();
}
render();
filtrniTanla("qolgan");
filtrniTanla("bajarilgan");
console.log(holat.ishlar.length);
</script>Konsolda:
hammasi: 3 ta
qolgan: 2 ta
bajarilgan: 1 ta
3Oxirgi qatorga qarang: filtr ishlarni o'chirmadi. Holatda hali ham 3 ta ish. Filtrlangan ro'yxat — hosila ma'lumot: u har render() da holatdan qayta hisoblanadi va hech qayerda saqlanmaydi.
Ikki funksiyaga bo'lganimizga ham e'tibor bering. mosmi — bitta ish haqida savolga javob beradi. korinadiganIshlar — butun ro'yxatni yig'adi. Ikkalasi ham faqat qiymat qaytaradi — ekranga tegmaydi va holatni o'zgartirmaydi. Bunday funksiyalarni alohida sinash oson.
6. "Hammasini qayta chizish"ning narxi
Qolip qulay, lekin bepul emas. Uch narxni biling.
6.1 Fokus yo'qoladi
"Bajarildi" tugmasini klaviatura bilan bosib ko'ring: Tab bilan tugmaga keling va Enter bosing. Ish belgilanadi — lekin fokus tugmadan yo'qoladi. render() eski tugmani o'chirib, o'rniga yangisini yaratdi, fokus esa o'chgan tugma bilan birga ketdi.
Biz buni Chrome'da tekshirdik: bosishdan keyin document.activeElement — body. Sichqoncha bilan ishlaydigan foydalanuvchi buni sezmaydi. Faqat klaviaturadan foydalanadigan Bobur esa har safar sahifa boshidan Tab bosib qaytishga majbur. Fokusni to'g'ri joyga qaytarishni Dinamik UI'da qulaylik darsida o'rganamiz.
6.2 Holatda bo'lmagan narsa yo'qoladi
Xuddi shu sabab bilan, render() chizadigan joydagi foydalanuvchining "qo'l bilan qilgan" hamma narsasi o'chadi: maydonga yozib qo'yilgan matn, ochilgan details, aylantirilgan joy. Shuning uchun "Yangi ish" maydonini render() chizmaydigan joyga — ro'yxatdan tashqariga — qo'ydik.
Qoida: ekranda saqlanishi kerak bo'lgan narsa holatda bo'lsin yoki render() ning tashqarisida tursin.
6.3 Katta ro'yxatda tezlik
10 ta ishni qayta chizish — sezilmaydi. 5000 ta qatorli jadvalni har bosishda noldan qurish esa sekinlashadi. Freymvorklar shu muammoni hal qiladi: ular yangi ko'rinishni xotirada quradi, eskisi bilan solishtiradi va DOM'da faqat o'zgargan joyni yangilaydi. React'dagi "Virtual DOM" va ro'yxatdagi har elementga beriladigan "kalit" (key) — aynan shu solishtirish uchun. Bizning id lar ham shunday kalit vazifasini o'taydi. Bu mexanizmni React qismida ko'rasiz — hozir bilish shart emas.
Vanilla JavaScript'da kichik va o'rta ro'yxatlar uchun "hammasini qayta chizish" — eng ishonchli tanlov. Kod soddaligi bir necha millisekunddan qimmatroq.
7. Qolip qoidalari
Bu darsdagi hamma narsani beshta qoidaga yig'amiz. Kelgusi loyihalaringizda shu ro'yxatni tekshiruv varag'i sifatida ishlating:
- Ma'lumot — faqat holatda. DOM'dan "nechta ish bor?" deb so'ramang, holatdan so'rang.
- Holat faqat maxsus funksiyalar orqali o'zgaradi —
ishQosh,ishniAlmashtir,filtrniTanla. - Har o'zgarishdan keyin —
render(). Istisnosiz. render()faqat o'qiydi — holatni o'zgartirmaydi; ekranni esa to'liq chizadi.- Hosila ma'lumot saqlanmaydi — soni, filtrlangan ro'yxat, jami summa har
render()da hisoblanadi.
Tekshirib ko'ring: Jasur aka so'radi: "Sahifa sarlavhasida ham qolgan ishlar soni chiqsin". Qaysi funksiyani o'zgartirasiz —
ishQosh,ishniAlmashtiryoki boshqasini?
Javob
Faqat render() ni. Qolgan ishlar soni — hosila ma'lumot: render() uni ishlar ro'yxatidan sanaydi va sarlavhaga yozadi. Holatni o'zgartiruvchi funksiyalar bu haqda hech narsa bilmaydi va o'zgarmaydi. Imperativ kodda esa har funksiyaga yangi qator qo'shishga to'g'ri kelardi — «Nega bu kerak?» bo'limidagi xato aynan shunday tug'iladi.
8. Ko'p uchraydigan xatolar
8.1 render() ni chaqirishni unutish
<ul id="ishlar"></ul>
<script>
const holat = { ishlar: ["Go'sht olish"] };
const royxat = document.getElementById("ishlar");
function render() {
const quti = document.createDocumentFragment();
for (const ish of holat.ishlar) {
const li = document.createElement("li");
li.textContent = ish;
quti.append(li);
}
royxat.replaceChildren(quti);
}
render();
holat.ishlar.push("Non olish");
console.log(holat.ishlar.length, royxat.children.length);
</script>Konsolda:
2 1Holatda ikkita ish, ekranda bitta. Holat o'zgardi, lekin hech kim ekranga aytmadi. Tuzatish: holatni faqat render() ni chaqiradigan funksiyalar orqali o'zgartiring («Qolip qoidalari»dagi 2- va 3-qoida) — shunda unutish imkoni qolmaydi.
8.2 DOM'ni to'g'ridan-to'g'ri o'zgartirish
Shoshib qolgan dasturchi "Bajarildi" ni holatsiz, to'g'ridan-to'g'ri qildi:
<style>
.bajarildi { text-decoration: line-through; }
</style>
<ul id="ishlar"></ul>
<script>
const holat = {
ishlar: [{ id: 1, matn: "Go'sht olish", bajarildi: false }],
};
const royxat = document.getElementById("ishlar");
function render() {
const quti = document.createDocumentFragment();
for (const ish of holat.ishlar) {
const li = document.createElement("li");
li.textContent = ish.matn;
if (ish.bajarildi) {
li.classList.add("bajarildi");
}
quti.append(li);
}
royxat.replaceChildren(quti);
}
render();
royxat.firstElementChild.classList.add("bajarildi");
console.log(`class: "${royxat.firstElementChild.className}"`);
render();
console.log(`class: "${royxat.firstElementChild.className}"`);
</script>Konsolda:
class: "bajarildi"
class: ""Birinchi qatorda class bor. Ikkinchisida — bo'sh: keyingi render() belgini "sehrli" tarzda o'chirib yubordi. Holatda bajarildi: false edi, render() esa holatga ishonadi. Real ilovada bu "bosgan narsam o'zi qaytib ketdi" degan shikoyatga aylanadi. Tuzatish: har o'zgarish — holat orqali («Qolip qoidalari»dagi 1- va 2-qoida).
8.3 id o'rniga indeks
ishniAlmashtir(1) ni "ro'yxatdagi 1-o'rindagi ish" deb yozsangiz, filtr yoqilganda xato chiqadi: ekranda ko'rinadigan 1-o'rin va holatdagi 1-o'rin — har xil ishlar. Ish o'chirilganda ham indekslar siljiydi. Tuzatish: har ishga o'zgarmas id bering va hamma joyda u bilan qidiring (find, findIndex).
8.4 Hosila ma'lumotni holatda saqlash
holat.korinadiganlar = [...] yoki holat.soni = 3 — bu «Nega bu kerak?» bo'limidagi muammoning qaytishi: bitta haqiqat yana ikki joyda. Tuzatish: «Qolip qoidalari»dagi 5-qoida — hisoblang, saqlamang.
9. Mashqlar
1-mashq (oson): Buyurtma savati
Savat holati tayyor. render() funksiyasini yozing: har taom uchun li — Osh × 2, pastda — Jami: 75 000 so'm. Oxirida render() ni chaqiring va jami matnini konsolga chiqaring.
Ishora: jami — sikl ichida narx * soni larni qo'shib boriladi (hosila ma'lumot). So'mni yozish uchun somYoz funksiyasi tayyor (Ko'p elementni samarali chizish darsidagi kabi).
<h3>Savat</h3>
<ul id="savat"></ul>
<p id="jami"></p>
<script>
const holat = {
taomlar: [
{ nom: "Osh", narx: 35000, soni: 2 },
{ nom: "Ko'k choy", narx: 5000, soni: 1 },
{ nom: "Lag'mon", narx: 28000, soni: 0 },
],
};
function somYoz(son) {
const raqamlar = String(son);
let natija = "";
for (let i = 0; i < raqamlar.length; i++) {
const qolgani = raqamlar.length - i;
if (i > 0 && qolgani % 3 === 0) {
natija += " ";
}
natija += raqamlar[i];
}
return natija;
}
// shu yerga yozing
</script>Yechim
<ul id="savat"></ul>
<p id="jami"></p>
<script>
const holat = {
taomlar: [
{ nom: "Osh", narx: 35000, soni: 2 },
{ nom: "Ko'k choy", narx: 5000, soni: 1 },
{ nom: "Lag'mon", narx: 28000, soni: 0 },
],
};
function somYoz(son) {
const raqamlar = String(son);
let natija = "";
for (let i = 0; i < raqamlar.length; i++) {
const qolgani = raqamlar.length - i;
if (i > 0 && qolgani % 3 === 0) {
natija += " ";
}
natija += raqamlar[i];
}
return natija;
}
const savat = document.getElementById("savat");
const jamiQator = document.getElementById("jami");
function render() {
const quti = document.createDocumentFragment();
let jami = 0;
for (const taom of holat.taomlar) {
const li = document.createElement("li");
li.textContent = `${taom.nom} × ${taom.soni}`;
quti.append(li);
jami += taom.narx * taom.soni;
}
savat.replaceChildren(quti);
jamiQator.textContent = `Jami: ${somYoz(jami)} so'm`;
}
render();
console.log(jamiQator.textContent);
</script>Konsolda:
Jami: 75 000 so'mHisob: 35 000 × 2 + 5 000 × 1 + 28 000 × 0 = 75 000. Lag'mon savatda turibdi, lekin soni 0 — u jamiga qo'shilmaydi. Natija tabida esa Lag'mon × 0 qatori ko'rinadi — keyingi mashqda uni yashiramiz.
2-mashq (o'rta): Bo'sh qatorlar va "Savat bo'sh"
1-mashqdagi render() ni yaxshilang: (1) soni 0 bo'lgan taomlar ro'yxatda chiqmasin; (2) birorta taom qolmasa, ro'yxat o'rnida Savat bo'sh yozuvi chiqsin. Sinash uchun: avval chizing, keyin hamma taomning soni ni 0 qiluvchi savatniTozala() funksiyasini yozib chaqiring. Konsolda: Jami: 75 000 so'm, Savat bo'sh.
Ishora: if (taom.soni === 0) continue; — continue shu aylanishni tashlab keyingisiga o'tadi. Bo'sh holatni sikldan keyin quti.childNodes.length === 0 bilan tekshiring. savatniTozala ham qolipga amal qilsin: holat → render().
Yechim
<ul id="savat"></ul>
<p id="jami"></p>
<script>
const holat = {
taomlar: [
{ nom: "Osh", narx: 35000, soni: 2 },
{ nom: "Ko'k choy", narx: 5000, soni: 1 },
{ nom: "Lag'mon", narx: 28000, soni: 0 },
],
};
function somYoz(son) {
const raqamlar = String(son);
let natija = "";
for (let i = 0; i < raqamlar.length; i++) {
const qolgani = raqamlar.length - i;
if (i > 0 && qolgani % 3 === 0) {
natija += " ";
}
natija += raqamlar[i];
}
return natija;
}
const savat = document.getElementById("savat");
const jamiQator = document.getElementById("jami");
function render() {
const quti = document.createDocumentFragment();
let jami = 0;
for (const taom of holat.taomlar) {
if (taom.soni === 0) {
continue;
}
const li = document.createElement("li");
li.textContent = `${taom.nom} × ${taom.soni}`;
quti.append(li);
jami += taom.narx * taom.soni;
}
if (quti.childNodes.length === 0) {
const li = document.createElement("li");
li.textContent = "Savat bo'sh";
quti.append(li);
jamiQator.textContent = "";
} else {
jamiQator.textContent = `Jami: ${somYoz(jami)} so'm`;
}
savat.replaceChildren(quti);
console.log(jamiQator.textContent || savat.textContent);
}
function savatniTozala() {
for (const taom of holat.taomlar) {
taom.soni = 0;
}
render();
}
render();
savatniTozala();
</script>Konsolda:
Jami: 75 000 so'm
Savat bo'shjamiQator.textContent || savat.textContent — qisqa tutashuv: jami bo'sh satr bo'lsa (falsy), ro'yxat matni olinadi. "Savat bo'sh" holati ham render() ning ishi — savatniTozala faqat ma'lumotni o'zgartirdi.
3-mashq (qiyin): Bajarilganlarni tozalash
«Kunlik ishlar» holatiga bajarilganlarniTozala() funksiyasini qo'shing: u bajarilgan hamma ishni holatdan olib tashlasin va render() ni chaqirsin. filter ishlatmang — for...of bilan yangi massiv yig'ib, holat.ishlar ga bering. Konsolda: 4 ta ish, keyin 2 ta ish, oxirida qolgan ishlar Choynaklarni yuvish, Non olish.
Ishora: yangi massivga faqat bajarildi bo'lmaganlarini push qiling. Ism ro'yxati — join.
<ul id="ishlar"></ul>
<script>
const holat = {
ishlar: [
{ id: 1, matn: "Go'sht olish", bajarildi: true },
{ id: 2, matn: "Choynaklarni yuvish", bajarildi: false },
{ id: 3, matn: "Gilam qoqish", bajarildi: true },
{ id: 4, matn: "Non olish", bajarildi: false },
],
};
const royxat = document.getElementById("ishlar");
function render() {
const quti = document.createDocumentFragment();
for (const ish of holat.ishlar) {
const li = document.createElement("li");
li.textContent = ish.matn;
quti.append(li);
}
royxat.replaceChildren(quti);
console.log(`${royxat.children.length} ta ish`);
}
// shu yerga bajarilganlarniTozala ni yozing
render();
</script>Yechim
<ul id="ishlar"></ul>
<script>
const holat = {
ishlar: [
{ id: 1, matn: "Go'sht olish", bajarildi: true },
{ id: 2, matn: "Choynaklarni yuvish", bajarildi: false },
{ id: 3, matn: "Gilam qoqish", bajarildi: true },
{ id: 4, matn: "Non olish", bajarildi: false },
],
};
const royxat = document.getElementById("ishlar");
function render() {
const quti = document.createDocumentFragment();
for (const ish of holat.ishlar) {
const li = document.createElement("li");
li.textContent = ish.matn;
quti.append(li);
}
royxat.replaceChildren(quti);
console.log(`${royxat.children.length} ta ish`);
}
function bajarilganlarniTozala() {
const qolganlar = [];
for (const ish of holat.ishlar) {
if (!ish.bajarildi) {
qolganlar.push(ish);
}
}
holat.ishlar = qolganlar;
render();
}
render();
bajarilganlarniTozala();
const nomlar = [];
for (const ish of holat.ishlar) {
nomlar.push(ish.matn);
}
console.log(nomlar.join(", "));
</script>Konsolda:
4 ta ish
2 ta ish
Choynaklarni yuvish, Non olishNega splice bilan sikl ichida o'chirmadik? Massivni aylanib turib undan element o'chirsangiz, indekslar siljiydi va har ikkinchi element tashlab ketiladi — Elementlarni tanlash darsidagi jonli ro'yxat tuzog'ining massivdagi egizagi. Yangi massiv yig'ish xavfsiz va tushunarli. holat ning o'zi const bo'lsa ham, uning ichidagi ishlar maydoniga yangi massiv berish mumkin: const faqat o'zgaruvchining o'zini qotiradi, obyekt ichini emas (Havola semantikasi).
4-mashq: Portfolio qadami — loyihalar filtri holatdan
Ko'p elementni samarali chizish darsida loyihalar sahifasiga teglar panelini qo'shdingiz, lekin tugmalar hali hech narsa qilmaydi. Endi ularni holat → render qolipiga ulaymiz. Kartalarning o'zi HTML'da qoladi — render() ularni qayta yaratmaydi, faqat qaysi biri ko'rinishini holatdan belgilaydi:
holatobyekti:{ teg: "hammasi" }.mosmi(karta)—teg"hammasi" bo'lsa yoki kartaningdata-texnologiyalarro'yxatida shu teg bo'lsatrue.render(): har kartagahiddenni holatdan qo'ying, ko'rinadiganlarnifor...ofbilan sanang va sonni Element yaratish darsidagi#loyihalar-sonihisoblagichiga yozing: "N ta loyiha ko'rsatilmoqda". Yangi hisoblagich yasamang — u bitta bo'lsin. Har tugmagaaria-pressedatributini qo'ying: tanlangani —"true", qolgani —"false"(Atributlar).filtrniUlash()— bir marta ishlaydi:.loyihalarbo'lmasa jim tugaydi, har[data-teg]tugmaga bosish tinglovchisini ulaydi (holat.tegni o'zgartirib,render()), oxiridarender()ni chaqiradi.
Ishora: aria-pressed — ekran o'quvchiga "bu tugma hozir bosilgan holatda" deydigan atribut. Malika NVDA bilan "HTML, bosilgan" deb eshitadi. Batafsil — Dinamik UI'da qulaylik darsida.
Yechim
asosiy.js ga, teglarPaneliniChiz() chaqiruvidan keyin:
const holat = { teg: "hammasi" };
function mosmi(karta) {
if (holat.teg === "hammasi") {
return true;
}
const teglar = karta.dataset.texnologiyalar.split(" ");
return teglar.includes(holat.teg);
}
function render() {
let soni = 0;
const kartalar =
document.querySelectorAll(".loyihalar > article");
for (const karta of kartalar) {
karta.hidden = !mosmi(karta);
if (!karta.hidden) {
soni++;
}
}
for (const tugma of document.querySelectorAll("[data-teg]")) {
const tanlangan = tugma.dataset.teg === holat.teg;
tugma.setAttribute("aria-pressed", String(tanlangan));
}
const yozuv = document.getElementById("loyihalar-soni");
yozuv.textContent = `${soni} ta loyiha ko'rsatilmoqda`;
}
function filtrniUlash() {
if (document.querySelector(".loyihalar") === null) {
return;
}
for (const tugma of document.querySelectorAll("[data-teg]")) {
tugma.addEventListener("click", () => {
holat.teg = tugma.dataset.teg;
render();
});
}
render();
}
filtrniUlash();Ko'p elementni samarali chizish darsidagi panel bilan birga sinab ko'ring:
<main>
<h1>Loyihalar</h1>
<p>
<button type="button" data-teg="hammasi">Hammasi</button>
<button type="button" data-teg="HTML">HTML</button>
<button type="button" data-teg="CSS">CSS</button>
</p>
<p id="loyihalar-soni">2 ta loyiha</p>
<div class="loyihalar">
<article data-texnologiyalar="HTML CSS">
<h2>Choyxona «Bahor» sayti</h2>
</article>
<article data-texnologiyalar="HTML">
<h2>Semantik blog maqolasi</h2>
</article>
</div>
</main>
<script>
const holat = { teg: "hammasi" };
function mosmi(karta) {
if (holat.teg === "hammasi") {
return true;
}
const teglar = karta.dataset.texnologiyalar.split(" ");
return teglar.includes(holat.teg);
}
function render() {
let soni = 0;
const kartalar =
document.querySelectorAll(".loyihalar > article");
for (const karta of kartalar) {
karta.hidden = !mosmi(karta);
if (!karta.hidden) {
soni++;
}
}
for (const tugma of document.querySelectorAll("[data-teg]")) {
const tanlangan = tugma.dataset.teg === holat.teg;
tugma.setAttribute("aria-pressed", String(tanlangan));
}
const yozuv = document.getElementById("loyihalar-soni");
yozuv.textContent = `${soni} ta loyiha ko'rsatilmoqda`;
console.log(yozuv.textContent);
}
function filtrniUlash() {
if (document.querySelector(".loyihalar") === null) {
return;
}
for (const tugma of document.querySelectorAll("[data-teg]")) {
tugma.addEventListener("click", () => {
holat.teg = tugma.dataset.teg;
render();
});
}
render();
}
filtrniUlash();
</script>Konsolda:
2 ta loyiha ko'rsatilmoqdaEndi CSS tugmasini bosing: blog maqolasi yashirinadi, konsolda 1 ta loyiha ko'rsatilmoqda. HTML — ikkala karta qaytadi va 2 ta. Hammasi — yana ikkalasi. Elements panelida (haqiqiy saytingizda) tugmalarning aria-pressed qiymati har bosishda almashganini ko'rasiz.
Nimasi "holat → render"? Tugma hech qaysi kartaga tegmaydi — u faqat holat.teg ni o'zgartiradi. Qaysi karta ko'rinishi, qaysi tugma bosilgan va yozuvdagi son — hammasi render() da, bitta holatdan. render() bu safar elementlarni qayta yaratmadi, lekin qoida o'sha: ekrandagi har o'zgaruvchan narsa holatdan qo'yiladi. Kartalar HTML'da qolgani uchun JavaScript ishlamasa ham, hamma loyiha ko'rinib turadi.
Uch nozik joy:
- Hisoblagich bitta. Element yaratish darsida
teglarniChizsahifaga#loyihalar-soniyozuvini ("2 ta loyiha") qo'ygan edi. Yoniga ikkinchi hisoblagich yasasak, CSS tanlanganda biri "2 ta loyiha", ikkinchisi "1 ta" derdi — sahifa o'zi bilan o'zi tortishardi, «Nega bu kerak?» bo'limidagi muammoning aynan o'zi. Shuning uchunrender()o'sha elementning matnini yangilaydi: endi u filtrni ham biladi.teglarniChizuni avvalgidek yaratadi,filtrniUlashesa undan keyin chaqiriladi —render()ga element tayyor turadi. hiddenva CSS. Portfolio'da.loyihalar > articlegadisplay: gridberilgan. Muallif CSS'idagidisplaybrauzerninghiddenqoidasini yengadi va karta yashirinmaydi.displayva yashirish darsida portfolio'ga[hidden]himoya qoidasini aynan shu kun uchun qo'shgan edingiz — u borligini tekshiring.idorqali topish.render()hisoblagichni har safargetElementById("loyihalar-soni")bilan topadi:teglarniChizqayta chaqirilib uni almashtirsa ham, yangi element topiladi.
Commit: JS: loyihalar filtrini holatga ula.
10. Real ishda
- React, Vue, Svelte (kursda alohida o'rganamiz) — bugungi qolipning sanoat versiyasi. Ularning shiori: "UI — holatning funksiyasi".
useStatevarenderso'zlarini React'da ko'rganingizda, bu darsni eslang: g'oya bir xil, faqat "nima o'zgardi"ni hisoblashni freymvork o'zi qiladi. - Holat boshqaruv kutubxonalari (Redux, Zustand) — katta ilovalarda holatni bitta "ombor"da saqlash va uni faqat maxsus funksiyalar orqali o'zgartirish. Bugungi 1- va 2-qoidalar ularning asosi.
- Saqlash oson bo'ladi. Hamma ma'lumot bitta obyektda bo'lgani uchun uni brauzerga yozib qo'yish va qaytarib olish bir necha qator. Buni 11-qismda
vazifalarilovasida qilasiz. - Bu qismning yakuniy loyihasi — vanilla JS vazifalar ro'yxati — aynan shu darsdagi "Kunlik ishlar"ning to'liq versiyasi: qo'shish, belgilash, o'chirish, filtr va tahrirlash.
- Intervyuda: "Imperativ va deklarativ farqi?", "Single source of truth nima?", "Nega React'da ro'yxat elementlariga key kerak?" — tez-tez beriladigan savollar. Oxirgisiga bugun yarim javob bera olasiz.
idsiz qaysi element qaysi ma'lumotga tegishli ekanini bilib bo'lmaydi.
Xulosa
- Bitta ma'lumot bir necha joyda yashasa (o'zgaruvchi, DOM, matn), qo'lda sinxronlash ertami-kechmi buziladi.
- Holat (state) — ilovaning hamma ma'lumoti bitta obyektda; DOM undan yasaladi, aksincha emas.
render()holatni o'qib, ekranni to'liq qayta chizadi; holatni o'zgartirmaydi.- Har harakat: holatni o'zgartiruvchi funksiya →
render(). - Hosila ma'lumot (soni, filtrlangan ro'yxat) saqlanmaydi, har chizishda hisoblanadi; filtr
for...of+ifbilan. - Narxi: fokus va holatdan tashqaridagi narsa yo'qoladi, katta ro'yxat sekinlashadi — freymvorklar buni solishtirish bilan hal qiladi.
Keyingi dars: addEventListener asoslari — shu darsda "qolip" sifatida ishlatgan tinglovchini to'liq o'rganamiz: qanday ulanadi, qanday olib tashlanadi va handler ichida nima bor.
Manbalar
- MDN: "Introduction to the DOM", "Document: createDocumentFragment()" — developer.mozilla.org
- React hujjatlari: "Thinking in React", "Choosing the State Structure" — react.dev
- web.dev: "Rendering performance" — web.dev
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!