Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. Nega aynan havola
- 3. Obyekt state
- 3.1 Spread bilan yangi obyekt
- 3.2 Ichma-ich obyekt: har qavat nusxalanadi
- 4. Massiv state
- 4.1 Qaysi metod joyida o'zgartiradi
- 4.2 Hammasi bitta misolda
- 4.3 Bitta elementni o'zgartirish: map va tegilmaganlar
- 5. Sayoz nusxa tuzog'i
- 5.1 structuredClone — chuqur nusxa
- 5.2 Immer: mutatsiya qilgandek yozish
- 6. TypeScript: readonly — mutatsiyani kompilyatsiyada to'xtatish
- 7. Vazifalar qadami: amallar.ts va o'zgarmas ro'yxat
- 7.1 v6 da nima edi
- 7.2 src/amallar.ts — sof funksiyalar
- 7.3 holat.ts: massiv va tinglovchi
- 7.4 asosiy.ts: nusxa endi kerak emas
- 7.5 Formani sahifaga ulash
- 7.6 Testlar avval yiqildi
- 7.7 Xulq farqi va ikki o'lchov
- 7.8 Commit
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qaysi biri ishlaydi?
- 2-mashq (o'rta): savatni boshqarish
- 3-mashq (qiyin): ichma-ich buyurtma (TypeScript)
- 4-mashq: Vazifalar qadami
- 10. Real ishda
- Xulosa
- Manbalar
State III: obyekt va massivlarni immutable yangilash — spread, map, filter va Immer
Qisqacha: React state o'zgarganini havola bo'yicha (
Object.is) aniqlaydi.list.push(x); setList(list)— o'sha massiv, React "o'zgarish yo'q" deb qayta chizmaydi. Shuning uchun state'dagi obyekt va massivni joyida o'zgartirmaymiz — yangisini yasaymiz: obyektga{ ...order, count: 3 }, massivga qo'shish[...list, x], o'chirishfilter, o'zgartirishmap, saralashtoSorted. Ichma-ich obyektda har qavat nusxalanadi. Tegilmagan elementlar esa o'sha-o'sha qoladi — nusxa arzon. Chuqur tuzilmalar uchun Immer "mutatsiya qilgandek" yozib, o'zgarmas natija beradi.
Bu darsda
- Nega
pushyokiobj.x = …dan keyin ekran yangilanmasliginiObject.isbilan tushuntira olasiz. - Obyekt state'ni spread bilan, ichma-ich obyektni har qavatini nusxalab yangilaysiz.
- Massivda qo'shish, o'chirish, o'zgartirish, saralash va almashtirishni o'zgartirmaydigan metodlar bilan qilasiz.
- Sayoz nusxaning tuzog'ini taniysiz,
structuredCloneva Immer qachon kerakligini bilasiz. vazifalarga sofamallar.tsvareadonlyro'yxatni qo'shib, branch'ni commit qilasiz.
Oldin bilishingiz kerak: State II: snapshot, batching va updater funksiya, Ichma-ich ma'lumotni immutable yangilash, Nusxa bilan o'zgartirish: toSorted, toReversed, toSpliced, with, Tenglik: ==, === va Object.is.
1. Nega bu kerak?
Sardor menyu muharririni yozdi: "Manti qo'shish" tugmasi. JavaScript odati bilan:
import { useState } from "react";
function App() {
const [dishes, setDishes] = useState(["Osh"]);
console.log("chizildi:", dishes.length);
function add() {
dishes.push("Manti"); // ❌ o'sha massivni o'zgartiryapti
setDishes(dishes);
console.log("massivda:", dishes.length);
}
return (
<>
<p>{dishes.join(", ")}</p>
<button onClick={add}>Manti qo'shish</button>
</>
);
}Konsolda:
chizildi: 1
massivda: 2Ekranda:
Osh
Manti qo'shishMassivda ikki taom, ekranda bitta. "chizildi" ham ikkinchi marta chiqmadi. Xato xabari yo'q — shuning uchun bu React'dagi eng ko'p uchraydigan va eng uzoq qidiriladigan xatolardan biri.
Sababi — Havola semantikasi darsidan. setDishes(dishes) React'ga o'sha massivni berdi. React esa eski va yangi qiymatni Object.is bilan solishtiradi: bir xil havola — "hech narsa o'zgarmagan", chizish shart emas. Ichida nima o'zgargani React'ni qiziqtirmaydi — u ichiga qaramaydi.
Hayotiy o'xshatish: kassadagi chek. Buyurtma o'zgarsa, kassir eski chekka ruchka bilan yozib qo'ymaydi — yangi chek chiqaradi. Oshxonaga yangi chek kelsa, darhol ko'radi: "bu boshqa chek — demak o'zgarish bor". Eski chek esa arxivda o'zgarmay qoladi. React ham shunday: yangi havola — yangi chek.
2. Nega aynan havola
React har elementni chuqur solishtirmaydi — bu juda qimmat bo'lardi: 1 000 vazifali ro'yxatda har bosishda 1 000 obyektni maydonma-maydon tekshirish. Havolani solishtirish esa bitta amal. Lekin bu ishlashi uchun kelishuv kerak: state'dagi obyekt va massiv o'zgarmas (immutable). O'zgarish bo'lsa — yangi obyekt, bo'lmasa — o'sha.
| Kod | Havola | React |
|---|---|---|
list.push(x); setList(list) |
o'sha | qayta chizmaydi |
setList([...list, x]) |
yangi | qayta chizadi |
order.count = 3; setOrder(order) |
o'sha | qayta chizmaydi |
setOrder({ ...order, count: 3 }) |
yangi | qayta chizadi |
Kelishuvning foydasi faqat React uchun emas. O'zgarmas ma'lumotda "oldingi holat" har doim butun qoladi: bekor qilish (undo), solishtirish, xatoni qayta takrorlash oson. Buni Immutability chuqur darsida ko'rgandik. vazifalar da ham bugun shu foyda paydo bo'ladi — «Vazifalar qadami» bo'limida.
3. Obyekt state
3.1 Spread bilan yangi obyekt
import { useState } from "react";
function App() {
const [order, setOrder] = useState({
dish: "Osh",
count: 1,
withTea: false,
});
return (
<>
<p>
{order.dish} × {order.count}
{order.withTea && " + ko'k choy"}
</p>
<button
onClick={() => setOrder({ ...order, count: order.count + 1 })}
>
Yana bitta
</button>
<button onClick={() => setOrder({ ...order, withTea: true })}>
Choy bilan
</button>
</>
);
}Ekranda:
Osh × 3 + ko'k choy
Yana bittaChoy bilan{ ...order, count: order.count + 1 } — spread: eski maydonlarning hammasi, keyin count ustidan yoziladi. Tartib muhim: spread oldin, o'zgargan maydon keyin (Props dagi qoidaning o'zi).
| Qadam | State | Ekranda |
|---|---|---|
| boshida | {dish: "Osh", count: 1, withTea: false} |
Osh × 1 |
| «Yana bitta» | yangi obyekt, count: 2 |
Osh × 2 |
| «Yana bitta» | yangi obyekt, count: 3 |
Osh × 3 |
| «Choy bilan» | yangi obyekt, withTea: true |
Osh × 3 + ko'k choy |
Har qadamda — yangi obyekt. Eskisini hech kim o'zgartirmadi.
Maslahat: Bir nechta mustaqil qiymat bitta obyektda bo'lishi shart emas.
countvawithTeani ikkitauseStatega ajratsa ham bo'ladi — spread kerak bo'lmaydi. Qachon obyekt, qachon alohida state — State strukturasini loyihalash darsida.
3.2 Ichma-ich obyekt: har qavat nusxalanadi
Buyurtmada ofitsiant ma'lumoti ham bor:
import { useState } from "react";
function App() {
const [order, setOrder] = useState({
table: 4,
waiter: { name: "Sardor", shift: "kunduzgi" },
});
function changeShift() {
setOrder({
...order, // 1-qavat: buyurtma
waiter: {
...order.waiter, // 2-qavat: ofitsiant
shift: "kechki",
},
});
}
return (
<>
<p>
{order.table}-stol, {order.waiter.name} ({order.waiter.shift})
</p>
<button onClick={changeShift}>Smenani almashtir</button>
</>
);
}Ekranda:
4-stol, Sardor (kechki)
Smenani almashtirO'zgargan maydongacha bo'lgan har qavat yangi obyekt bo'ladi: buyurtma va ofitsiant. { ...order, waiter: { ...order.waiter, shift } } — Ichma-ich ma'lumotni immutable yangilash darsidagi naqsh. Qavatlardan birini unutsangiz — order.waiter.shift = "kechki" kabi mutatsiya bo'ladi.
flowchart LR
subgraph Eski
O1["order"] --> W1["waiter: kunduzgi"]
end
subgraph Yangi
O2["order (yangi)"] --> W2["waiter (yangi): kechki"]
end
O2 -. "table: 4 — o'sha qiymat" .-> O1Diagrammada: yangi order va yangi waiter — ikkita yangi obyekt, eski ikkitasi butun qoldi.
4. Massiv state
4.1 Qaysi metod joyida o'zgartiradi
Massivda ham qoida o'sha: state'dagi massivni joyida o'zgartirmang. JavaScript'ning ko'p metodi aynan joyida o'zgartiradi — ularning har biri uchun o'zgartirmaydigan juftligi bor:
| Ish | joyida (mutatsiya) | yangi massiv |
|---|---|---|
| qo'shish | push, unshift |
[...arr, x], [x, ...arr] |
| o'chirish | splice, pop, shift |
filter, toSpliced |
| o'zgartirish | arr[i] = x |
map, with(i, x) |
| o'rtaga qo'yish | splice(i, 0, x) |
toSpliced(i, 0, x) |
| saralash | sort, reverse |
toSorted, toReversed |
toSorted, toReversed, toSpliced, with — ES2023 metodlari (Nusxa bilan o'zgartirish). Ular aynan React kabi kodlar uchun qo'shilgan.
4.2 Hammasi bitta misolda
«Bahor» menyu muharriri: qo'shish, o'chirish, narxni o'zgartirish, saralash. Har tugma yangi massiv yasaydi:
import { useState } from "react";
function App() {
const [menu, setMenu] = useState([
{ id: 1, name: "Osh", price: 35000 },
{ id: 2, name: "Manti", price: 30000 },
]);
return (
<>
<ol>
{menu.map((d) => (
<li key={d.id}>
{d.name} — {d.price}
</li>
))}
</ol>
<button
onClick={() =>
setMenu([...menu, { id: 3, name: "Somsa", price: 8000 }])
}
>
Somsa
</button>
<button
onClick={() =>
setMenu(menu.map((d) => ({ ...d, price: d.price * 1.1 })))
}
>
+10%
</button>
<button
onClick={() =>
setMenu(menu.toSorted((a, b) => a.price - b.price))
}
>
Arzonidan
</button>
<button
onClick={() => setMenu(menu.filter((d) => d.id !== 2))}
>
Manti ×
</button>
</>
);
}Ekranda:
Somsa — 8800
Osh — 38500
Somsa+10%ArzonidanManti ×Blok to'rt tugmani ketma-ket bosdi. Natija oynasida ularni istalgan tartibda bosib ko'ring. Har tugma — bitta qator kod:
- qo'shish —
[...menu, yangi]; - narxni o'zgartirish —
map, har element uchun{ ...d, price }— massiv ham, o'zgargan obyektlar ham yangi; - saralash —
toSorted(sort—menuning o'zini buzardi); - o'chirish —
filter.
Bu narxlarda kasr chiqmadi: 35000 * 1.1 — aniq 38500. Lekin ko'paytirish har doim butun son bermaydi (Pul va aniq hisob-kitob). Haqiqiy kassada natijani Math.round bilan yaxlitlang.
4.3 Bitta elementni o'zgartirish: map va tegilmaganlar
Eng ko'p uchraydigan amal — ro'yxatdagi bitta obyektni o'zgartirish:
setMenu(
menu.map((d) => (d.id === id ? { ...d, soldOut: true } : d)),
);Ternary: id mos bo'lsa — yangi obyekt, aks holda — d ning o'zi. Natija: yangi massiv, ichida bitta yangi obyekt va qolganlari o'sha-o'sha. Bu strukturaviy bo'lishish (structural sharing) deyiladi (Immutability chuqur). 1 000 vazifali ro'yxatda bitta katak bosilsa — 1 000 havola nusxalanadi, lekin faqat bitta obyekt yaratiladi. Arzon.
React buning foydasini ham ko'radi. Tegilmagan elementlar o'sha obyekt — keyinchalik memo ular uchun qayta chizishni butunlay o'tkazib yuborishi mumkin.
Tekshirib ko'ring:
setMenu(menu.map((d) => { d.price += 1000; return d; }))— yangi massiv yaratildi-ku. Muammo bormi?
Javob
Bor. Massiv yangi, lekin ichidagi obyektlar o'sha — ular joyida o'zgartirildi. Ekran yangilanadi (massiv havolasi yangi), lekin eski state ham buzildi. Eski massivni boshqa joy saqlagan bo'lsa (undo tarixi, boshqa komponent), u ham o'zgarib qoladi. To'g'risi: menu.map((d) => ({ ...d, price: d.price + 1000 })).
5. Sayoz nusxa tuzog'i
Spread faqat birinchi qavatni nusxalaydi (Sayoz va chuqur nusxalash). Bu tuzoqqa olib keladi:
import { useState } from "react";
const saved = [{ id: 1, dish: "Osh", done: false }];
function App() {
const [list, setList] = useState(saved);
function markDone() {
const copy = [...list];
copy[0].done = true; // ❌ obyekt o'sha — saved ham o'zgaradi
setList(copy);
console.log("saved[0].done:", saved[0].done);
}
return (
<>
<p>
{list[0].dish}: {list[0].done ? "tayyor" : "kutilmoqda"}
</p>
<button onClick={markDone}>Tayyor deb belgila</button>
</>
);
}Konsolda:
saved[0].done: trueEkranda:
Osh: tayyor
Tayyor deb belgilaEkran to'g'ri — massiv yangi, React qayta chizdi. Lekin saved (masalan, serverdan kelgan asl nusxa) ham buzildi. "Bekor qilish" endi asl holatni topa olmaydi. Tuzatish — o'zgarayotgan obyektni ham nusxalash: list.map((d, i) => (i === 0 ? { ...d, done: true } : d)).
5.1 structuredClone — chuqur nusxa
structuredClone(list) hamma qavatni nusxalaydi (Sayoz va chuqur nusxalash). Keyin nusxani bemalol o'zgartirish mumkin. Lekin u state yangilash uchun kam ishlatiladi:
- qimmat — o'zgarmagan qismlarni ham nusxalaydi (strukturaviy bo'lishish yo'qoladi);
- havolalar yo'qoladi — hamma element yangi, React va memo "hamma narsa o'zgardi" deb ko'radi;
- funksiya va klass nusxalarini ko'chira olmaydi.
Qachon o'rinli: tashqi manbadan kelgan ma'lumotni "o'zimizniki" qilib olishda, bir marta. Har bosishda emas.
5.2 Immer: mutatsiya qilgandek yozish
Chuqur tuzilmada spread'lar zinapoyasi uzun va xatoga moyil. Immer kutubxonasi boshqa yo'l beradi: siz "qoralama" (draft) ustida oddiy mutatsiya yozasiz, Immer esa o'zgarmas natija yasaydi. Biz Node 24 da sinab ko'rdik (npm i immer@11.1.21, 2026-10 holatiga joriy versiya):
import { produce } from "immer";
const order = {
table: 4,
waiter: { name: "Sardor", shift: "kechki" },
items: [
{ id: 1, dish: "Osh", count: 2 },
{ id: 2, dish: "Ko'k choy", count: 1 },
],
};
const next = produce(order, (draft) => {
draft.items[1].count += 2; // "mutatsiya" — lekin draft'da
draft.items.push({ id: 3, dish: "Non", count: 1 });
});
console.log(order.items.length, next.items.length);
console.log(order.items[1].count, next.items[1].count);
console.log(next.items[0] === order.items[0]); // tegilmagan — o'sha
console.log(next.waiter === order.waiter);
console.log(Object.isFrozen(next));node immer.mjs chiqishi:
2 3
1 3
true
true
trueorder butun qoldi (2 ta taom, choy 1). next — yangi, lekin tegilmagan items[0] va waiter — o'sha obyektlar: strukturaviy bo'lishish avtomatik. Immer natijani muzlatadi ham (Object.isFrozen — true): keyingi tasodifiy mutatsiya xato beradi. React bilan qulay ishlatish uchun use-immer paketi bor (0.11.0, React 19 ni qo'llaydi) — useImmer hook'i. Bu blok saytda ishga tushmaydi: natija oynasi faqat react paketlarini yuklaydi. Immer'ni o'z loyihangizda sinab ko'ring. Uni reducer bilan birga — Reducer + Immer darsida chuqur ko'ramiz.
Qachon Immer kerak? Tuzilma 3+ qavat chuqur bo'lsa yoki yangilashlar ko'p bo'lsa. Oddiy ro'yxat va formalar uchun spread, map va filter yetadi — vazifalar da Immer yo'q.
6. TypeScript: readonly — mutatsiyani kompilyatsiyada to'xtatish
Mutatsiya jim xato. TypeScript uni ovozli qiladi — readonly bilan (Massiv va tuple turlari, Obyekt turlari):
type Dish = { readonly id: number; readonly name: string };
const menu: readonly Dish[] = [{ id: 1, name: "Osh" }];
menu.push({ id: 2, name: "Manti" });
menu[0].name = "Palov";
export const result = menu;Xato:
TS2339: Property 'push' does not exist on type 'readonly Dish[]'.
TS2540: Cannot assign to 'name' because it is a read-only property."readonly Dish[] turida push xususiyati yo'q" va "name — faqat o'qiladigan xususiyat, unga yozib bo'lmaydi". readonly massivda push, sort, splice kabi joyida o'zgartiradigan metodlar umuman yo'q. map, filter, toSorted esa bor — ular yangi massiv qaytaradi. Turlar ish vaqtida yo'qoladi, lekin xato kod hech qachon build'gacha yetmaydi.
7. Vazifalar qadami: amallar.ts va o'zgarmas ro'yxat
State I darsida boshlangan refactor/forma-usestate branch'ini bugun yakunlaymiz. Maqsad — holat.vazifalar o'zgarmas massiv bo'lsin, uni o'zgartiradigan funksiyalar esa yangi massiv qaytarsin.
7.1 v6 da nima edi
v6 da ro'yxat — VazifalarRoyxati klassi (Yakuniy loyiha: vazifalar v2 dan beri). Uning metodlari ichki massivni joyida o'zgartirardi:
qosh(matn: string): Vazifa {
…
this.#vazifalar.push(vazifa);
this.#xabarBer();
}Ekranni yangilash uchun klassda kuzatuvchi bor edi (obuna, #xabarBer). React'ga o'tganda bu ikki muammo beradi. Birinchisi — massiv o'sha qolib, React o'zgarishni sezmaydi («Nega bu kerak?» bo'limi). Ikkinchisi — server xato qaytarsa, oldingi holatga qaytish uchun avval nusxa olish kerak edi (holat.vazifalar.toJSON()).
7.2 src/amallar.ts — sof funksiyalar
Yangi fayl:
// amallar.ts — vazifalar ro'yxatini o'zgartiruvchi SOF funksiyalar
// (17/#14). Hech biri massivni yoki vazifani joyida o'zgartirmaydi:
// yangi massiv qaytaradi, tegilmagan vazifalar — o'sha
// obyektlarning o'zi (nusxa emas). React o'zgarishni havola
// bo'yicha (Object.is) sezadi: push() yoki v.bajarildi = true dan
// keyin massiv "o'sha-o'sha" — ekran yangilanmaydi. v6 dagi
// VazifalarRoyxati.qosh/almashtir... shu yerga ko'chdi (17/#14)
import { Vazifa } from "./vazifa.ts";
import type { VazifaMalumoti } from "./turlar.ts";
type Vazifalar = readonly VazifaMalumoti[];
/**
* Yangi id — eng kattasidan keyingisi. Alohida hisoblagich
* saqlanmaydi: u ro'yxatdan kelib chiqadi (hosilaviy qiymat, 17/#15)
*/
export function keyingiId(vazifalar: Vazifalar): number {
return vazifalar.reduce((eng, v) => Math.max(eng, v.id), 0) + 1;
}
/** @returns oxiriga yangi vazifa qo'shilgan YANGI massiv */
export function qosh(vazifalar: Vazifalar, matn: string): Vazifalar {
const yangi = { id: keyingiId(vazifalar), matn, bajarildi: false };
return [...vazifalar, yangi];
}
/**
* @returns id li vazifaning bajarildi'si teskarisiga o'zgargan
* massiv
*/
export function almashtir(vazifalar: Vazifalar, id: number): Vazifalar {
return vazifalar.map((v) =>
v.id === id ? { ...v, bajarildi: !v.bajarildi } : v,
);
}
/**
* Bo'sh yoki juda uzun matn qabul qilinmaydi — o'sha massivning
* o'zi qaytadi (havola o'zgarmadi = o'zgarish yo'q)
*/
export function matnniOzgartir(
vazifalar: Vazifalar,
id: number,
matn: string,
): Vazifalar {
const toza = matn.trim();
if (toza === "" || toza.length > Vazifa.MAKS_UZUNLIK) {
return vazifalar;
}
return vazifalar.map((v) => (v.id === id ? { ...v, matn: toza } : v));
}
export function ochir(vazifalar: Vazifalar, id: number): Vazifalar {
return vazifalar.filter((v) => v.id !== id);
}Izohlar telefon uchun qayta bo'lingan, kod qatorlari kanondagidek. Bugungi darsning hammasi shu 50 qatorda:
qosh—[...vazifalar, yangi]: qo'shish.almashtir—map+ ternary +{ ...v, bajarildi: !v.bajarildi }: bitta elementni o'zgartirish, qolganlari — o'sha obyektlar.matnniOzgartir— noto'g'ri matnda o'sha massivni qaytaradi. Havola o'zgarmadi — "o'zgarish yo'q". Chaqiruvchi buni===bilan bilishi mumkin.ochir—filter.keyingiId— id endi alohida hisoblagichda emas, ro'yxatdan hisoblanadi (Pure komponentlar va State strukturasi g'oyasi: hosilaviy qiymat).readonly VazifaMalumoti[]— «TypeScript:readonly» bo'limidagi himoya: bu funksiyalar ichidapushyozilsa,tscto'xtatadi.
Hech biri React'ni import qilmaydi. Ular — oddiy sof funksiyalar: node:test da build'siz sinaladi.
7.3 holat.ts: massiv va tinglovchi
holat.vazifalar endi klass emas, readonly massiv. O'zgartiruvchilar amallar.ts ni chaqiradi va yangi massivni yozadi:
export type Holat = {
- vazifalar: VazifalarRoyxati;
+ // readonly massiv: push/splice ni tsc to'xtatadi — faqat
+ // almashtirish
+ vazifalar: readonly VazifaMalumoti[];
…
+function vazifalarniYoz(yangi: readonly VazifaMalumoti[]): void {
+ holat.vazifalar = yangi;
+ tinglovchi();
+}
…
export function vazifaniAlmashtir(id: number): void {
- holat.vazifalar.almashtir(id);
+ vazifalarniYoz(almashtir(holat.vazifalar, id));
}VazifalarRoyxati klassi qoldi, lekin endi faqat o'qiydi: filtr, statistika, teglar. Uni kerak joyda royxatniOl() massivdan yasaydi. Klassning obuna va mutatorlari olib tashlandi — xabarni endi holat.ts dagi bitta tinglovchi beradi.
7.4 asosiy.ts: nusxa endi kerak emas
Eng chiroyli o'zgarish — server bilan ishlashda. Server rad etsa, ro'yxat oldingi holatga qaytadi. v6 da buning uchun nusxa olinardi:
- // Nusxa — qaytarish uchun: mahalliy() ro'yxatni joyida
- // o'zgartiradi
- const oldingi = holat.vazifalar.toJSON();
+ // Qaytarish uchun eski ro'yxat. Nusxa shart emas (17/#14):
+ // mahalliy() uni o'zgartirmaydi, yangi massiv yasaydi — eskisi
+ // butun qoladi
+ const oldingi = holat.vazifalar;
mahalliy();oldingi — shunchaki havola. Hech kim eski massivni o'zgartirmaydi — u "arxivdagi chek" kabi butun qoladi. «Nega aynan havola» bo'limidagi va'da qilingan foyda — mana shu.
7.5 Formani sahifaga ulash
State I da yozilgan YangiVazifaForma endi sahifaga chiqadi. index.html dagi <form id="yangi-forma"> o'rnida bo'sh joy qoladi:
- <form id="yangi-forma" novalidate>
- …
- </form>
+ <!-- Yangi vazifa formasi — React (17/#12) -->
+ <div id="yangi-forma-joyi"></div>render.tsx dagi formaniChiz() uni uchinchi React oroli sifatida chizadi. U flushSync bilan chiziladi: forma birinchi kadrdayoq bo'ladi va ro'yxat pastga "sakramaydi" (CLS 0). Shuningdek asosiy.ts dan formaning beshta vanilla tinglovchisi (submit, input, keydown, takliflar click va keydown — 50 qatorga yaqin) va xatoniKorsat/xatoniTozala o'chdi. O'rniga — vazifaniQosh(matn): QoshishNatijasi.
7.6 Testlar avval yiqildi
Bu safar testlar avval yiqildi — va bu yaxshi belgi. npm test (holat massivga o'tgandan keyin, testlar hali eski):
ℹ tests 146
ℹ pass 132
ℹ fail 1414 ta eski test VazifalarRoyxati ning mutatorlarini (qosh, obuna…) va o'zgaruvchan holat ni sinardi. Ular yangi kod bilan endi mos emas — xulq o'zgardi va testlar buni ko'rsatdi. Eski testlarning keraksizi olib tashlandi, qolgani yangi API'ga moslandi. Yangi fayl — tekshiruv/amallar.test.js, 7 ta test. Uning boshidagi g'oya:
// Muzlatilgan: biror amal joyida o'zgartirsa — TypeError
// (strict mode)
const boshlangich = Object.freeze([
Object.freeze({ id: 1, matn: "Non olish", bajarildi: false }),
…
]);Test ma'lumoti Object.freeze bilan muzlatilgan. Biror funksiya ichida push yoki v.bajarildi = … qolib ketsa — TypeError. Muzlatish — o'zgarmaslikning ish vaqtidagi qorovuli, readonly esa kompilyatsiyadagisi. Testlardan biri strukturaviy bo'lishishni ham tekshiradi:
test("almashtir: faqat o'sha vazifa yangi obyekt", () => {
const yangi = almashtir(boshlangich, 1);
assert.equal(yangi[0].bajarildi, true);
assert.equal(boshlangich[0].bajarildi, false);
assert.notEqual(yangi[0], boshlangich[0]);
assert.equal(yangi[1], boshlangich[1]);
});assert.equal(yangi[1], boshlangich[1]) — === bilan: tegilmagan vazifa o'sha obyekt.
Natija (node --test, oxirgi qatorlar):
ℹ tests 149
ℹ suites 37
ℹ pass 149
ℹ fail 0Vitest — 14/14 (State I dagi forma testlari bilan). lint, format:check, tip — toza.
7.7 Xulq farqi va ikki o'lchov
Id. v6 da o'chirilgan eng katta id qayta berilmasdi — hisoblagich alohida edi. Endi id ro'yxatdan hisoblanadi: 4-vazifa o'chirilsa, keyingi yangisi yana 4 oladi. Bu hujjatlangan farq: server id'ni baribir o'zi beradi, TEXNIK-QARZ.md ga yozildi. Test ham bor: "id hisoblagichda emas, ro'yxatdan: o'chirilgan eng katta id qaytadi".
Brauzer tekshiruvi. Forma React'ga o'tgach, 159 tekshiruvning bir qismi yiqildi. Ssenariy yozishni taqlid qilardi: maydon.value = "…" va new Event("input"). Bunday sun'iy hodisa ko'tarilmaydi (bubbles: false). React esa hodisalarni ildizda ushlaydi — Hodisalar darsidagi "React ham delegatsiya qiladi". Brauzerning haqiqiy input hodisasi doim ko'tariladi. Shuning uchun ssenariy tuzatildi: new Event("input", { bubbles: true }), to'rt joyda. v6 build'i ham shu ssenariy bilan 159/159 o'tadi. Ya'ni tekshiruv qattiqlashmadi, haqiqiy brauzerga yaqinlashdi. Natija: 159/159.
knip. npm run knip bitta narsa topadi:
Unused exports (1)
tegTakliflari function src/render.tsx:129:17tegTakliflari endi faqat render.tsx ning ichida (formaniChiz) ishlatiladi — eksport ortiqcha. Kanonda u hozircha qoldirilgan: State'ni ko'tarish darsida funksiya teglar.ts ga ko'chadi va eksport haqiqatan kerak bo'ladi. Siz export ni hozir olib tashlasangiz ham bo'ladi.
7.8 Commit
refactor: yangi vazifa formasi — useState; ro'yxat o'zgarmas massiv
YangiVazifaForma (xato va takliflar — state). amallar.ts — sof
qosh/almashtir/ochir/matnniOzgartir; holat.vazifalar — readonly
VazifaMalumoti[], VazifalarRoyxati faqat o'qiydi.Diff: 16 fayl, +579 −326. Uch dars — bitta branch, bitta PR, gh pr merge --merge.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| bosildi, ekran yangilanmadi, xato yo'q | push/obj.x = va o'sha havola |
[...arr, x], { ...obj, x } |
| ekran yangilandi, lekin asl ma'lumot buzildi | sayoz nusxadagi obyektni o'zgartirish | o'zgargan obyektni ham spread |
| ichma-ich maydon yangilanmadi | bitta qavat nusxalangan | har qavat: { ...a, b: { ...a.b, c } } |
| boshqa joydagi tartib buzildi | sort/reverse |
toSorted/toReversed |
Property 'push' does not exist on type 'readonly …' |
readonly himoyasi |
yangi massiv yasang |
| hamma qator qayta chiziladi | structuredClone har bosishda |
map + tegilmaganini qaytaring |
9. Mashqlar
Mashqlar kurs/mashqlar/17/14-immutable/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): qaysi biri ishlaydi?
setOrder va setList chaqiruvlaridan qaysilari ekranni yangilaydi va asl ma'lumotni buzmaydi?
// A
order.count++; setOrder(order);
// B
setOrder({ ...order, count: order.count + 1 });
// C
setList([...list, "Non"]);
// D
setList(list.sort());
// E
setList(list.toSorted());
// F
const copy = [...list]; copy[0].done = true; setList(copy);- To'g'rilari (harflar, alifbo tartibida):
Yechim
B, C, E. A — o'sha obyekt, ekran yangilanmaydi. D — sort list ni joyida o'zgartiradi va o'sha massivni qaytaradi: havola o'zgarmadi, ekran yangilanmaydi. F — massiv yangi, ekran yangilanadi, lekin copy[0] — asl obyekt: u buzildi.
2-mashq (o'rta): savatni boshqarish
Cart komponenti: state — items, massiv: [{ id: 1, dish: "Osh", count: 1 }, { id: 2, dish: "Choy", count: 2 }]. Har qatorda "+", "−" va "×" tugmalari (aria-label: "Osh +", "Osh −", "Osh ×"). Talablar:
- "+" —
countni 1 ga oshiradi (map); - "−" — kamaytiradi;
count1 bo'lsa, qator o'chadi (map, keyinfilter); - "×" — o'chiradi (
filter); - "Non +1" — oxiriga
{ id: 3, dish: "Non", count: 1 }; - pastda — jami dona (
reduce, state emas).
Blok: "Osh +", "Choy −", "Choy −", "Non +1". Ekranda:
Osh × 2 +−×
Non × 1 +−×
Non +1
Jami: 3 donaYechim
import { useState } from "react";
function Cart() {
const [items, setItems] = useState([
{ id: 1, dish: "Osh", count: 1 },
{ id: 2, dish: "Choy", count: 2 },
]);
function change(id, delta) {
const changed = items.map((i) =>
i.id === id ? { ...i, count: i.count + delta } : i,
);
setItems(changed.filter((i) => i.count > 0));
}
const total = items.reduce((sum, i) => sum + i.count, 0);
return (
<>
<ul>
{items.map((i) => (
<li key={i.id}>
{i.dish} × {i.count}{" "}
<button
aria-label={`${i.dish} +`}
onClick={() => change(i.id, 1)}
>
+
</button>
<button
aria-label={`${i.dish} −`}
onClick={() => change(i.id, -1)}
>
−
</button>
<button
aria-label={`${i.dish} ×`}
onClick={() =>
setItems(items.filter((x) => x.id !== i.id))
}
>
×
</button>
</li>
))}
</ul>
<button
onClick={() =>
setItems([...items, { id: 3, dish: "Non", count: 1 }])
}
>
Non +1
</button>
<p>Jami: {total} dona</p>
</>
);
}
function App() {
return <Cart />;
}Ekranda:
Osh × 2 +−×
Non × 1 +−×
Non +1
Jami: 3 dona"−" ikki amalni zanjir qiladi: map (kamaytirish) va filter (nolga tushganini olib tashlash) — ikkalasi ham yangi massiv. total — reduce bilan har chizishda hisoblanadi (reduce).
3-mashq (qiyin): ichma-ich buyurtma (TypeScript)
Stol buyurtmasi, .tsx da. Tur:
type Item = {
readonly id: number;
readonly dish: string;
readonly count: number;
};
type Order = {
readonly table: number;
readonly guest: { readonly name: string; readonly phone: string };
readonly items: readonly Item[];
};Yozing:
renameGuest(order, name)— mehmon ismini o'zgartiradi (ikki qavat);addItem(order, dish)—itemsoxiriga yangi taom, id — eng kattasidan +1;increment(order, id)— bitta taomningcounti +1;- uchalasi ham sof (yangi
Orderqaytaradi,readonlybuni majburlaydi); App—useState<Order>, uch tugma va ekranda:5-stol, Malika: Osh × 3, Non × 1.
Blok: "Ism", "Osh +1", "Osh +1", "Non". Boshlang'ich: 5-stol, mehmon "Mehmon", Osh × 1.
Yechim
import { useState } from "react";
type Item = {
readonly id: number;
readonly dish: string;
readonly count: number;
};
type Order = {
readonly table: number;
readonly guest: { readonly name: string; readonly phone: string };
readonly items: readonly Item[];
};
function renameGuest(order: Order, name: string): Order {
return { ...order, guest: { ...order.guest, name } };
}
function addItem(order: Order, dish: string): Order {
const ids = order.items.map((i) => i.id);
const maxId = Math.max(0, ...ids);
const item = { id: maxId + 1, dish, count: 1 };
return { ...order, items: [...order.items, item] };
}
function increment(order: Order, id: number): Order {
return {
...order,
items: order.items.map((i) =>
i.id === id ? { ...i, count: i.count + 1 } : i,
),
};
}
const start: Order = {
table: 5,
guest: { name: "Mehmon", phone: "+998 90 000 00 00" },
items: [{ id: 1, dish: "Osh", count: 1 }],
};
export default function App() {
const [order, setOrder] = useState<Order>(start);
const lines = order.items.map((i) => `${i.dish} × ${i.count}`);
return (
<>
<p>
{order.table}-stol, {order.guest.name}: {lines.join(", ")}
</p>
<button onClick={() => setOrder(renameGuest(order, "Malika"))}>
Ism
</button>
<button onClick={() => setOrder(increment(order, 1))}>
Osh +1
</button>
<button onClick={() => setOrder(addItem(order, "Non"))}>
Non
</button>
</>
);
}Ekranda:
5-stol, Malika: Osh × 3, Non × 1
IsmOsh +1NonFunksiyalar komponentdan tashqarida va sof. Ularni vazifalar dagi amallar.ts kabi alohida faylga chiqarib, node:test bilan sinash mumkin. readonly tufayli order.items.push(…) yoki order.guest.name = … yozib bo'lmaydi — tsc to'xtatadi. start obyekti esa hech qachon o'zgarmaydi: undo kerak bo'lsa, u tayyor.
4-mashq: Vazifalar qadami
«Vazifalar qadami» bo'limidagi o'zgarishlarni kiriting: amallar.ts, holat.ts, royxat.ts va vazifa.ts (mutatorlar olib tashlanadi), render.tsx (formaniChiz, royxatniOl), asosiy.ts, index.html, testlar. Avval faqat holat.ts ni o'zgartirib npm test ni ishga tushiring — nechta test yiqildi? Keyin testlarni yangilang va npm run check toza bo'lsin. Commit — bo'limdagi xabar bilan.
Yechim
Tekshiruv ro'yxati:
- O'tish paytida yiqilgan testlar — kutilgan: ular eski, o'zgaruvchan API'ni sinaydi. Har yiqilgan testni o'qing — "bu xulq kerakmi?" — keyin yangisiga ko'chiring yoki olib tashlang.
node --test— 149/149,amallar.test.jsdagi muzlatilgan ro'yxat bilan.TypeError: Cannot add property …chiqsa — biror funksiya joyida o'zgartiryapti.- Vitest — 14/14.
npm run tip—holat.vazifalar.push(…)qolgan bo'lsa,TS2339beradi.- Brauzerda: vazifa qo'shing, belgilang, o'chiring; internetni o'chirib o'chirishga urining — ro'yxat oldingi holatiga qaytishi kerak (
oldingi— havola).
Yakuniy kod — refactor: yangi vazifa formasi — useState; ro'yxat o'zgarmas massiv commit'i.
10. Real ishda
- Immutable yangilash — React, Redux, Zustand va boshqa holat kutubxonalarining umumiy qoidasi. Redux Toolkit ichida Immer ishlatadi.
- Kod ko'rigida eng ko'p ushlanadigan React xatolaridan biri — state'ni joyida o'zgartirish. TypeScript'da
readonlyva ESLint qoidalari buni oldindan to'xtatadi. - Undo/redo, "optimistik" yangilash (server javobini kutmasdan ekranni o'zgartirish va xatoda qaytarish) — o'zgarmas ma'lumot bilan oson: eski holat — shunchaki havola.
- Intervyu savollari: "Nega state'ni mutatsiya qilib bo'lmaydi?", "
sortvatoSortedfarqi?", "Sayoz va chuqur nusxa?", "Immer qanday ishlaydi?".
Xulosa
- React state o'zgarishini havola (
Object.is) bo'yicha sezadi: joyida o'zgartirilgan obyekt yoki massiv — "o'zgarish yo'q". - Obyekt:
{ ...obj, maydon }; ichma-ich — har qavat nusxalanadi. - Massiv: qo'shish — spread, o'chirish —
filter, o'zgartirish —map, saralash —toSorted, almashtirish —with. - Tegilmagan elementlar o'sha obyekt qoladi — strukturaviy bo'lishish arzon va foydali; sayoz nusxadagi obyektni o'zgartirmang.
structuredClone— kam holatda; chuqur tuzilmalar uchun Immer; TypeScript'dareadonlymutatsiyani kompilyatsiyada to'xtatadi.vazifalardaamallar.ts— sof yangilash funksiyalari,holat.vazifalar—readonlymassiv, qaytarish uchun nusxa kerak emas; 149 + 14 test, 159/159.
Keyingi dars: State strukturasini loyihalash — state'da nima saqlash, nimani hisoblash kerakligini va vazifalar da bitta App ildizini yig'ishni o'rganamiz.
Manbalar
- React: "Updating Objects in State", "Updating Arrays in State" — react.dev/learn
- MDN:
Array.prototype.toSorted,with,toSpliced— developer.mozilla.org - Immer — immerjs.github.io/immer; use-immer — github.com/immerjs/use-immer
- MDN:
structuredClone()— developer.mozilla.org/docs/Web/API/Window/structuredClone
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!