Mundarija (22)
- Bu darsda
- 1. Nega bu kerak?
- 2. Immer bilan ichma-ich holat
- 2.1 Spread piramidasi
- 2.2 Reducer'ni produce bilan o'rash
- 2.3 useImmerReducer
- 2.4 Immer tuzoqlari
- 3. Imkonsiz holatlar
- 3.1 Boolean'lar bilan bron formasi
- 3.2 Bitta status
- 4. Holat mashinasi
- 4.1 Holatlar va o'tishlar
- 4.2 Reducer — holat mashinasi
- 5. Kutubxonalar: XState va Zag
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): imkonsiz holatlarni sanang
- 2-mashq (o'rta): rasm yuklash mashinasi
- 3-mashq (qiyin): ikki filial, Immer bilan
- 8. Real ishda
- Xulosa
- Manbalar
React reducer + Immer va holat mashinasi: ichma-ich holat va "imkonsiz holatlar"siz UI
Qisqacha: Chuqur ichma-ich holatni spread bilan yangilash uzun va xatoga moyil. Immer'ning
producei (React'da —use-immerpaketidagiuseImmerReducer) reducer'da qoralamani "joyida o'zgartirgandek" yozishga imkon beradi, natija esa baribir yangi, muzlatilgan obyekt. Ikkinchi g'oya — holat mashinasi:isLoading,isError,isSuccesskabi bir nechta boolean o'rniga bittastatusmaydoni ("kiritish" | "yuborilmoqda" | "tasdiqlandi" | "xato"). Shunda "ham yuklanmoqda, ham xato" kabi imkonsiz holat yozib bo'lmaydigan bo'ladi. Reducer esa har holatda faqat ruxsat etilgan hodisalarni qabul qiladi.
Bu darsda
- Ichma-ich holatni spread va Immer bilan yangilashni taqqoslaysiz.
produceni reducer'da vauseImmerReducerni komponentda ishlatasiz, Immer xatolarini taniysiz.- Boolean bayroqlar keltirib chiqaradigan "imkonsiz holatlar"ni sanab, ularni
statusbilan yo'qotasiz. - Holat mashinasini jadval va diagramma sifatida chizib, reducer'ga aylantirasiz.
- XState va Zag qachon kerakligini bilasiz.
Oldin bilishingiz kerak: useReducer, Immutability chuqur (Immer), State III: immutable yangilash, Discriminated union.
1. Nega bu kerak?
«Bahor» endi ikki filialda: Chilonzor va Yunusobod. Kassa ekranida har filialning buyurtmalari, har buyurtmada taomlar. Sardor reducer'da "Chilonzordagi 1-buyurtmaga yana bitta osh" amalini yozdi va to'rt qavatli spread hosil bo'ldi. Ertasiga bitta qavsni noto'g'ri yopdi — Yunusobodning buyurtmalari yo'qolib qoldi.
Ikkinchi muammo bron formasida. Sardor uchta state yozdi: isLoading, isError, isSuccess. Bir kuni Jasur aka ekranda bir vaqtning o'zida "Yuborilmoqda…" va "Xato yuz berdi" yozuvlarini ko'rdi. Qayta urinish tugmasi isLoading ni yoqdi-yu, isError ni o'chirishni unutdi.
Ikkala muammoning ildizi bir: holat shakli kodni xatoga undaydi. Bugun ikkalasini ham tuzatamiz. Birinchisini — Immer bilan, ikkinchisini — holat mashinasi tafakkuri bilan.
Hayotiy o'xshatish: svetofor. Uchta chiroq bor, lekin ular uchta mustaqil kalit emas. Svetofor faqat bir nechta aniq holatda bo'ladi (qizil, sariq, yashil) va ular orasida aniq tartib bilan o'tadi. "Qizil va yashil birga yongan" holat — yo'q, chunki u qurilmaning o'zida mumkin emas. Holat mashinasi — shunday svetofor.
2. Immer bilan ichma-ich holat
2.1 Spread piramidasi
Immutability chuqur darsida Immer'ning produce funksiyasi bilan tanishgan edik: "joyida yozing — o'zgarmas oling". Endi uni reducer'da ishlatamiz. Avval muammoni ko'ramiz — bir xil o'zgarish ikki usulda:
import { produce } from "immer";
const state = {
branches: [
{ id: "chilonzor", orders: [{ id: 1, dish: "Osh", qty: 1 }] },
{ id: "yunusobod", orders: [{ id: 2, dish: "Manti", qty: 3 }] },
],
};
// spread bilan: Chilonzordagi 1-buyurtma soni +1
const bySpread = {
...state,
branches: state.branches.map((b) =>
b.id !== "chilonzor"
? b
: {
...b,
orders: b.orders.map((o) =>
o.id === 1 ? { ...o, qty: o.qty + 1 } : o,
),
},
),
};
// Immer bilan: xuddi o'sha o'zgarish
const byImmer = produce(state, (draft) => {
const branch = draft.branches.find((b) => b.id === "chilonzor");
branch.orders.find((o) => o.id === 1).qty += 1;
});
console.log(JSON.stringify(bySpread) === JSON.stringify(byImmer));
const before = state.branches[0].orders[0].qty;
console.log(before, byImmer.branches[0].orders[0].qty);
console.log(byImmer.branches[1] === state.branches[1]);
const order = byImmer.branches[0].orders[0];
console.log(Object.isFrozen(byImmer), Object.isFrozen(order));
console.log(produce(state, () => {}) === state);Bu blok tashqi paket (immer 11.1.21) ishlatadi — saytda «Ishga tushir» yo'q. Uni toza papkada (npm i immer@11.1.21) Node 24 da ishga tushirdik:
true
1 2
true
true true
trueHar qatorning ma'nosi:
true— ikki usul bir xil natija berdi.1 2— asl holat o'zgarmadi (1), yangisida 2.true— Yunusobod filiali yangi holatda o'sha obyekt: Immer tegilmagan qismni nusxalamadi — State III dagi strukturaviy bo'lishish (structural sharing). React uchun bu muhim: shu filialni ko'rsatadigan komponent "o'zgarish yo'q" deb biladi.true true— natija muzlatilgan (freeze): keyin uni tasodifan o'zgartirib bo'lmaydi.true— retsept hech narsani o'zgartirmasa, asl holatning o'zi qaytadi — React qayta chizmaydi.
2.2 Reducer'ni produce bilan o'rash
produce ga faqat retsept bersangiz, u reducer qaytaradi (bunday shakl "curried" deyiladi — argumentlarni bo'lib olish):
import { produce } from "immer";
const cartReducer = produce((draft, action) => {
switch (action.type) {
case "added": {
const item = draft.find((i) => i.name === action.name);
if (item) item.qty += 1;
else draft.push({ name: action.name, qty: 1 });
break;
}
case "removed":
return draft.filter((i) => i.name !== action.name);
default:
throw new TypeError(`Noma'lum amal: ${action.type}`);
}
});Uch narsaga e'tibor bering:
added— qoralamani "o'zgartiradi" va hech narsa qaytarmaydi (break). Immer o'zgarishlarni yig'ib, yangi holat yasaydi.removed— yangi massivni qaytaradi. Butun holatni almashtirish kerak bo'lsa, shu usul.- Ikkalasini birga qilish mumkin emas (pastda).
useReducer darsidagi push xatosi bu yerda xato emas: draft — asl holat emas, uning Proxy "qoralamasi".
2.3 useImmerReducer
React'da buning tayyor hook'i bor — use-immer paketi (0.11.0, peer: immer >=8.0.0, react ^16.8.0 … ^19.0.0). Paket kodi juda qisqa. useImmerReducer — bor-yo'g'i useReducer(useMemo(() => produce(reducer), [reducer]), …). Ya'ni yuqoridagi o'rashni o'zi qiladi.
import { useImmerReducer } from "use-immer";
function cartReducer(draft, action) {
switch (action.type) {
case "added": {
const item = draft.find((i) => i.name === action.name);
if (item) item.qty += 1;
else draft.push({ name: action.name, qty: 1 });
break;
}
case "removed":
return draft.filter((i) => i.name !== action.name);
default:
throw new TypeError(`Noma'lum amal: ${action.type}`);
}
}
export function Cart() {
const [cart, dispatch] = useImmerReducer(cartReducer, []);
const text = cart.map((i) => `${i.name}×${i.qty}`).join(", ");
const add = () => dispatch({ type: "added", name: "Osh" });
const remove = () => dispatch({ type: "removed", name: "Osh" });
return (
<div>
<p>{text || "bo'sh"}</p>
<button onClick={add}>Osh</button>
<button onClick={remove}>Olib tashlash</button>
</div>
);
}Saytdagi «Ishga tushir» faqat react ni yuklaydi, shuning uchun bu blokda tugma yo'q. O'z loyihangizda npm i immer@11.1.21 use-immer@0.11.0 qiling. Biz komponentni React 19.3.0, use-immer 0.11.0, immer 11.1.21 bilan jsdom'da chizdik: «Osh», «Osh», «Olib tashlash» bosildi. <p> matni har qadamda:
bo'sh
Osh×1
Osh×2
bo'sh2.4 Immer tuzoqlari
Immer'ning o'z qoidalari bor. Ularni buzsangiz, aniq xato oladi (hammasi Node 24 + immer 11.1.21 da olingan):
O'zgartirish va qaytarish birga. Strelkali funksiyada figurali qavssiz yozilsa, tasodifan bo'ladi:
[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."Immer retsepti yangi qiymat qaytardi va qoralamani o'zgartirdi. Yo u, yo bu." (d) => d.items.push("Manti") — push uzunlikni qaytaradi, Immer uni "yangi qiymat" deb oladi. Qavs qo'ying: (d) => { d.items.push("Manti"); }.
Qoralamani tashqarida saqlash. produce tugagach, qoralama "bekor qilinadi" — Proxy o'chiriladi. Uni o'zgaruvchiga saqlab, keyin ishlatsangiz:
TypeError: Cannot perform 'get' on a proxy that has been revoked"Bekor qilingan proxy ustida get bajarib bo'lmaydi." Qoralama faqat retsept ichida yashaydi. setTimeout, await yoki effektdan unga murojaat qilmang.
Natijani o'zgartirish. Immer natijani muzlatadi:
TypeError: Cannot add property 2, object is not extensible"2-xususiyatni qo'shib bo'lmaydi, obyekt kengaytirilmaydi." Bu — himoya: natijaga push qilish xatosi darhol ko'rinadi, ekran jim eskirib qolmaydi.
Tekshirib ko'ring: Savatda ikki daraja bor:
cart.items[i].qty. Immer kerakmi?
Javob
Shart emas. Ikki darajani spread bilan yozish hali o'qiladi: items.map((i) => i.name === n ? { ...i, qty: i.qty + 1 } : i). Immer — qo'shimcha paket va qo'shimcha qoidalar. U holat uch-to'rt daraja chuqur bo'lganda yoki bir amal ko'p joyni o'zgartirganda o'zini oqlaydi. Holatni tekisroq qilish (State strukturasi) ham ko'pincha Immer'siz yechim beradi.
3. Imkonsiz holatlar
3.1 Boolean'lar bilan bron formasi
State strukturasi darsida "imkonsiz holatlar" bilan tanishgan edik: uch boolean — 8 kombinatsiya, ma'nolisi to'rtta. Endi ular reducer bilan qanday yo'qotilishini ko'ramiz. Sardorning bron formasi — uchta mustaqil bayroq:
import { useState } from "react";
function BookingForm() {
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
function submit() {
setIsLoading(true);
setTimeout(() => { // server "xato" qaytardi
setIsLoading(false);
setIsError(true);
}, 50);
}
function retry() {
setIsLoading(true); // ❌ isError ni o'chirish unutildi
}
return (
<div>
{isLoading && <p>Yuborilmoqda…</p>}
{isError && <p>Xato yuz berdi</p>}
{isSuccess && <p>Stol bron qilindi!</p>}
<button onClick={submit}>Yuborish</button>{" "}
<button onClick={retry}>Qayta urinish</button>
</div>
);
}
export default BookingForm;Ekranda:
Yuborilmoqda…
Xato yuz berdi
Yuborish Qayta urinishJasur aka ko'rgan ekran — aynan shu. Uchta boolean — 2³ = 8 ta kombinatsiya. Ulardan nechtasi ma'noli?
isLoading |
isError |
isSuccess |
Ma'nosi |
|---|---|---|---|
| false | false | false | kiritish |
| true | false | false | yuborilmoqda |
| false | true | false | xato |
| false | false | true | tasdiqlandi |
| true | true | false | yuklanmoqda va xato |
| true | false | true | yuklanmoqda va tasdiqlangan |
| false | true | true | xato va tasdiqlangan |
| true | true | true | hammasi birga |
8 tadan 4 tasi imkonsiz holat — haqiqatda bo'lmasligi kerak, lekin kod ularni yozishga ruxsat beradi. Har setX chaqiruvida dasturchi qolgan ikkitasini to'g'ri qo'yishni eslab turishi kerak. To'rtinchi bayroq qo'shilsa — 16 ta kombinatsiya, ulardan 11 tasi imkonsiz.
3.2 Bitta status
Yechim — bayroqlar o'rniga bitta maydon, u faqat ruxsat etilgan qiymatlardan birini oladi. TypeScript'da bu discriminated union (Discriminated union). Bundan ham yaxshisi: har holatga faqat o'ziga tegishli ma'lumot biriktiriladi:
type Booking =
| { status: "kiritish" }
| { status: "yuborilmoqda" }
| { status: "tasdiqlandi"; bookingId: string }
| { status: "xato"; message: string };Endi bron raqami faqat "tasdiqlandi" holatida bor, xato matni — faqat "xato" da. TypeScript buni kuzatadi:
type Booking =
| { status: "kiritish" }
| { status: "yuborilmoqda" }
| { status: "tasdiqlandi"; bookingId: string }
| { status: "xato"; message: string };
function Message({ booking }: { booking: Booking }) {
if (booking.status === "xato") {
return <p>Bron raqami: {booking.bookingId}</p>;
}
return null;
}
export default Message;Xato:
TS2339: Property 'bookingId' does not exist on type '{ status: "xato"; message: string; }'."Bu turda bookingId yo'q." if ichida TypeScript booking ni "xato" variantiga toraytirdi. Unda bron raqami yo'q — xato bo'lgan bronning raqami ham bo'lmaydi. Imkonsiz holatni yaratishga ham urinib ko'ramiz:
type Booking =
| { status: "kiritish" }
| { status: "yuborilmoqda" }
| { status: "tasdiqlandi"; bookingId: string }
| { status: "xato"; message: string };
const booking: Booking = {
status: "yuborilmoqda",
message: "Tarmoq xatosi",
};Xato:
TS2353: Object literal may only specify known properties, and 'message' does not exist in type '{ status: "yuborilmoqda"; }'."Obyekt literalida faqat ma'lum xususiyatlar bo'ladi; 'yuborilmoqda' turida message yo'q." "Yuborilmoqda va xato" — turning o'zida yozib bo'lmaydi.
vazifalar da bunday tur allaqachon bor: TarmoqHolati = "yuklanmoqda" | "tayyor" | "xato" | "oflayn". Aloqa yozuvi to'rt boolean emas, bitta holatdan chiziladi. Bu tasodif emas — ilova boshidan shu tafakkur bilan loyihalangan.
4. Holat mashinasi
4.1 Holatlar va o'tishlar
Holat mashinasi (finite state machine, "chekli holatlar mashinasi") — tizim faqat sanab chiqilgan holatlardan birida bo'ladi va bir holatdan boshqasiga faqat ruxsat etilgan hodisa bilan o'tadi. Bron formasi uchun:
stateDiagram-v2
[*] --> kiritish
kiritish --> yuborilmoqda: YUBOR
yuborilmoqda --> tasdiqlandi: TASDIQ
yuborilmoqda --> xato: XATO
xato --> yuborilmoqda: YUBOR
tasdiqlandi --> [*]Diagrammada strelkasi yo'q narsa — mumkin emas. "Yuborilmoqda" holatida YUBOR yo'q, demak tugmani ikki marta bosish ikkinchi so'rovni yubormaydi. "Tasdiqlandi" dan chiqish yo'q.
Xuddi shu mashina jadval ko'rinishida — reducer'ning shartnomasi:
| Holat \ Hodisa | YUBOR | TASDIQ | XATO |
|---|---|---|---|
| kiritish | → yuborilmoqda | — | — |
| yuborilmoqda | — | → tasdiqlandi | → xato |
| xato | → yuborilmoqda | — | — |
| tasdiqlandi | — | — | — |
"—" — hodisa e'tiborsiz qoldiriladi: holat o'zgarmaydi.
4.2 Reducer — holat mashinasi
Jadvalni reducer'ga aylantiramiz. Avval holat bo'yicha, keyin hodisa bo'yicha tanlaymiz:
import { useReducer } from "react";
function bookingReducer(state, event) {
console.log(state.status, "+", event.type);
switch (state.status) {
case "kiritish":
case "xato":
if (event.type === "YUBOR") return { status: "yuborilmoqda" };
return state;
case "yuborilmoqda":
if (event.type === "TASDIQ") {
return { status: "tasdiqlandi", bookingId: event.bookingId };
}
if (event.type === "XATO") {
return { status: "xato", message: event.message };
}
return state;
case "tasdiqlandi":
return state;
default:
throw new TypeError(`Noma'lum holat: ${state.status}`);
}
}
const SUBMIT = { type: "YUBOR" };
// server javoblari o'rnida
const SERVER_ERROR = { type: "XATO", message: "Tarmoq xatosi" };
const SERVER_OK = { type: "TASDIQ", bookingId: "B-17" };
function BookingForm() {
const [booking, send] = useReducer(bookingReducer, {
status: "kiritish",
});
return (
<div>
<p>Holat: {booking.status}</p>
{booking.status === "xato" && <p>{booking.message}</p>}
{booking.status === "tasdiqlandi" && (
<p>Bron raqami: {booking.bookingId}</p>
)}
<button onClick={() => send(SUBMIT)}>Yubor</button>{" "}
<button onClick={() => send(SERVER_ERROR)}>Xato</button>{" "}
<button onClick={() => send(SERVER_OK)}>OK</button>
</div>
);
}
export default BookingForm;Konsolda:
kiritish + YUBOR
yuborilmoqda + YUBOR
yuborilmoqda + XATO
xato + YUBOR
yuborilmoqda + TASDIQEkranda:
Holat: tasdiqlandi
Bron raqami: B-17
Yubor Xato OK«Xato» va «OK» tugmalari — server javobining o'rnida (haqiqiy so'rovni effektlar darsida yozgan edik). Amal → holat jadvali:
| Qadam | Hodisa | Holat oldin | Holat keyin |
|---|---|---|---|
| 1 | YUBOR | kiritish | yuborilmoqda |
| 2 | YUBOR (ikkinchi bosish) | yuborilmoqda | yuborilmoqda — e'tiborsiz |
| 3 | XATO | yuborilmoqda | xato ("Tarmoq xatosi") |
| 4 | YUBOR | xato | yuborilmoqda — xato matni o'z-o'zidan yo'qoldi |
| 5 | TASDIQ | yuborilmoqda | tasdiqlandi ("B-17") |
Ikkinchi qadamga qarang: tugma ikki marta bosildi, lekin ikkinchi so'rov "yuborilmadi" — mashina buni rad etdi. To'rtinchi qadamda xato matni alohida tozalanmadi. Yangi holat obyektida u yo'q, shuning uchun "xato va yuborilmoqda" bo'lishi mumkin emas. Boolean'lardagi xatoni yozishning iloji qolmadi.
Maslahat: Hodisa nomlari bu yerda katta harf bilan (
YUBOR) — holat mashinasi kutubxonalarida shunday an'ana.useReducerdarsidagi "o'tgan zamon" (added) bilan ziddiyat yo'q: ikkalasi ham "nima bo'ldi" degan xabar, faqat yozilish uslubi boshqa.
Tekshirib ko'ring: "Tasdiqlandi" holatidan keyin foydalanuvchi yana bron qilmoqchi. Mashinaga nima qo'shasiz?
Javob
Yangi o'tish: tasdiqlandi --YANGI--> kiritish. Reducer'da case "tasdiqlandi": if (event.type === "YANGI") return { status: "kiritish" };. Diagrammaga bitta strelka, jadvalga bitta katak — o'zgarish faqat shu joyda. Qolgan holatlar YANGI ni e'tiborsiz qoldiradi.
5. Kutubxonalar: XState va Zag
Qo'lda yozilgan reducer-mashina kichik oqimlar uchun yetarli. Oqim murakkablashsa — ichma-ich holatlar ("to'lov" ichida "karta" va "Click"), parallel holatlar, taymerlar ("30 soniyada javob bo'lmasa — xato"), shartli o'tishlar — maxsus kutubxonalar bor. Ular bilan Holat mashinalari: XState darsida ishlaymiz. Hozir — tanishuv.
XState (xstate 5.33.2, React uchun @xstate/react 6.1.0; npm, 2026-10-13) — holat mashinalari va "statechart"lar kutubxonasi. Yuqoridagi bron mashinasi XState'da:
import { createActor, createMachine } from "xstate";
const bookingMachine = createMachine({
id: "bron",
initial: "kiritish",
states: {
kiritish: { on: { YUBOR: "yuborilmoqda" } },
yuborilmoqda: {
on: { TASDIQ: "tasdiqlandi", XATO: "xato" },
},
xato: { on: { YUBOR: "yuborilmoqda" } },
tasdiqlandi: { type: "final" },
},
});
const actor = createActor(bookingMachine);
actor.subscribe((snapshot) => console.log(snapshot.value));
actor.start();
actor.send({ type: "TASDIQ" }); // kiritishda yo'q — e'tiborsiz
actor.send({ type: "YUBOR" });
actor.send({ type: "XATO" });
actor.send({ type: "YUBOR" });
actor.send({ type: "TASDIQ" });
console.log("status:", actor.getSnapshot().status);Toza papkada xstate@5.33.2 bilan Node 24 da ishga tushirdik:
kiritish
kiritish
yuborilmoqda
xato
yuborilmoqda
tasdiqlandi
status: doneMashina — oddiy obyekt: bizning jadval deyarli so'zma-so'z. Ikkinchi kiritish — rad etilgan TASDIQ: kuzatuvchi xabar oldi, holat esa o'sha. type: "final" — yakuniy holat, shuning uchun oxirida status: done. React'da @xstate/react dagi useMachine(bookingMachine) [snapshot, send] qaytaradi — useReducer ga juda o'xshash.
Zag (@zag-js/react 1.46.0) — boshqa yo'nalish: UI komponentlari (menyu, dialog, slayder, combobox) uchun tayyor holat mashinalari. Ark UI va Chakra UI kabi kutubxonalar ichida ishlaydi. Siz "ochiq/yopiq, fokus qayerda, klaviatura nima qiladi" mantig'ini yozmaysiz — mashina tayyor, siz faqat ko'rinishni chizasiz.
| Vosita | Qachon |
|---|---|
useState |
bitta-ikkita mustaqil qiymat |
useReducer + status |
bir nechta hodisa, 3–6 holat (bron, to'lov, yuklash) |
useImmerReducer |
chuqur ichma-ich holat |
| XState | ko'p bosqichli, ichma-ich/parallel holatlar, taymerlar |
| Zag (va undan qurilgan UI kutubxonalari) | tayyor murakkab UI vidjetlari |
6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| ekranda "Yuborilmoqda" va "Xato" birga | mustaqil boolean'lar | bitta status |
| tugmani ikki marta bosish ikki so'rov yubordi | holat hodisani tekshirmaydi | "yuborilmoqda" da YUBOR ni e'tiborsiz qoldiring |
[Immer] An immer producer returned a new value *and* modified its draft… |
qavssiz strelka yoki return + o'zgartirish |
yoki o'zgartiring, yoki qaytaring |
Cannot perform 'get' on a proxy that has been revoked |
qoralama retseptdan tashqarida ishlatildi | qoralama faqat retsept ichida |
Cannot add property …, object is not extensible |
Immer natijasini o'zgartirish | yangi produce yoki dispatch |
TS2339: Property 'bookingId' does not exist… |
holatni tekshirmasdan ma'lumot o'qildi | avval status ni tekshiring |
7. Mashqlar
Mashqlar kurs/mashqlar/17/40-holat-mashinasi/ da. 3-mashq uchun papkada npm i immer@11.1.21.
1-mashq (oson): imkonsiz holatlarni sanang
Taom rasmini yuklash komponentida to'rtta boolean bor: isIdle, isLoading, isLoaded, isFailed. Ma'noli holatlar — har safar faqat bittasi true.
- Jami kombinatsiyalar soni:
- Ma'noli holatlar soni:
- Imkonsiz holatlar soni:
Yechim
const flags = 4;
const all = 2 ** flags;
const valid = flags; // har safar faqat bittasi true
console.log(all, valid, all - valid);Konsolda:
16 4 12To'rtta bayroq o'rniga status: "kutish" | "yuklanmoqda" | "yuklandi" | "xato" — 4 qiymat, 0 imkonsiz holat. isIdle umuman kerak emas edi: u "boshqa hech biri emas" degani.
2-mashq (o'rta): rasm yuklash mashinasi
TypeScript'da imageReducer(state, event) yozing. Holatlar: kutish, yuklanmoqda, yuklandi (url bilan), xato (message bilan, attempts — nechanchi urinish). Hodisalar: YUKLA, TAYYOR (url), XATO (message). Qoidalar:
kutishvaxatodanYUKLA→yuklanmoqda;yuklanmoqdadanTAYYOR→yuklandi,XATO→xato(attempts— oldingi xatodagi son + 1, birinchi xatoda 1);xatodaattempts2 ga yetgan bo'lsa,YUKLAe'tiborsiz (boshqa urinish yo'q);- qolgan hamma kombinatsiya — o'sha holat.
Ishora: attempts ni yuklanmoqda holatida ham saqlash kerak bo'ladi (nechanchi urinish ketayotgani).
Yechim
import { useReducer } from "react";
export type ImageState =
| { status: "kutish" }
| { status: "yuklanmoqda"; attempts: number }
| { status: "yuklandi"; url: string }
| { status: "xato"; message: string; attempts: number };
export type ImageEvent =
| { type: "YUKLA" }
| { type: "TAYYOR"; url: string }
| { type: "XATO"; message: string };
export const MAX_ATTEMPTS = 2;
export function imageReducer(
state: ImageState,
event: ImageEvent,
): ImageState {
switch (state.status) {
case "kutish":
return event.type === "YUKLA"
? { status: "yuklanmoqda", attempts: 0 }
: state;
case "xato":
if (event.type === "YUKLA" && state.attempts < MAX_ATTEMPTS) {
return { status: "yuklanmoqda", attempts: state.attempts };
}
return state;
case "yuklanmoqda":
if (event.type === "TAYYOR") {
return { status: "yuklandi", url: event.url };
}
if (event.type === "XATO") {
return {
status: "xato",
message: event.message,
attempts: state.attempts + 1,
};
}
return state;
case "yuklandi":
return state;
}
}
export default function DishImage() {
const [state, send] = useReducer(imageReducer, {
status: "kutish",
});
const failed = state.status === "xato";
return (
<div>
<p>
{state.status}
{failed && ` (${state.attempts}): ${state.message}`}
</p>
<button onClick={() => send({ type: "YUKLA" })}>
Yukla
</button>{" "}
<button onClick={() => send({ type: "XATO", message: "404" })}>
Xato
</button>
</div>
);
}Ekranda:
xato (2): 404
Yukla XatoIkki marta xato bo'lgach, uchinchi «Yukla» e'tiborsiz qoldi. E'tibor bering: switch (state.status) da default yo'q. Lekin TypeScript hamma holatlar qamrab olinganini biladi — funksiya har yo'lda ImageState qaytaradi. attempts faqat kerakli holatlarda bor — kutish va yuklandi da bu maydon umuman yo'q.
3-mashq (qiyin): ikki filial, Immer bilan
«Bahor» kassasi holati: { branches: [{ id, orders: [{ id, dish, qty, ready }] }] }. Immer'ning produce i bilan reducer yozing. Amallar:
{ type: "qtyChanged", branchId, orderId, qty }— sonni o'zgartiradi;qty0 bo'lsa — buyurtma o'chadi;{ type: "allReady", branchId }— filialdagi hamma buyurtmaready: true;{ type: "branchClosed", branchId }— filial ro'yxatdan chiqadi.
Mavjud bo'lmagan filial yoki buyurtma — o'sha holat qaytsin (Immer'da buning uchun qoralamani o'zgartirmaslik yetadi). Tekshiring: tegilmagan filial yangi holatda o'sha obyekt bo'lsin.
Yechim
import { produce } from "immer";
export const cashierReducer = produce((draft, action) => {
const branch = draft.branches.find((b) => b.id === action.branchId);
if (!branch) return; // o'zgarish yo'q — asl holat qaytadi
switch (action.type) {
case "qtyChanged": {
const { orders } = branch;
const i = orders.findIndex((o) => o.id === action.orderId);
if (i === -1) return;
if (action.qty <= 0) orders.splice(i, 1);
else orders[i].qty = action.qty;
break;
}
case "allReady":
for (const o of branch.orders) o.ready = true;
break;
case "branchClosed":
draft.branches = draft.branches.filter((b) => b !== branch);
break;
default:
throw new TypeError(`Noma'lum amal: ${action.type}`);
}
});
const order = (id, dish, qty) => ({ id, dish, qty, ready: false });
const state = {
branches: [
{ id: "chilonzor", orders: [order(1, "Osh", 1)] },
{ id: "yunusobod", orders: [order(2, "Manti", 3)] },
],
};
const send = (s, action) => cashierReducer(s, action);
const a = send(state, { type: "allReady", branchId: "chilonzor" });
console.log(a.branches[0].orders[0].ready);
console.log(a.branches[1] === state.branches[1]);
const b = send(a, {
type: "qtyChanged", branchId: "x", orderId: 1, qty: 2,
});
console.log(b === a);
const c = send(a, { type: "branchClosed", branchId: "yunusobod" });
console.log(c.branches.map((x) => x.id));npm i immer@11.1.21 bilan Node 24 da:
true
true
true
[ 'chilonzor' ]Filial topilmasa, retsept hech narsa qaytarmaydi (return;). Immer'da bu "o'zgarish yo'q" degani: asl holat qaytadi va React qayta chizmaydi. Filial yopilganda draft.branches ga yangi massiv tayinlandi — bu ham qoralamani o'zgartirish, qaytarish emas. splice va o.ready = true — oddiy mutatsiya sintaksisi, lekin natija baribir yangi, muzlatilgan obyekt.
8. Real ishda
- Redux Toolkit reducer'larini ichida Immer bilan yozadi —
createSlicedagistate.value += 1aynan qoralama (Redux Toolkit I). Immer'ni bilgan dasturchi RTK kodini o'qishda qiynalmaydi. - Server holati (yuklanmoqda/tayyor/xato) — TanStack Query kabi kutubxonalar ham
status: "pending" | "error" | "success"beradi, boolean'lar o'rniga (TanStack Query I). - To'lov, ro'yxatdan o'tish, bron kabi ko'p bosqichli oqimlar holat mashinasi sifatida loyihalanadi; diagramma kod ko'rigida ham, mahsulot menejeri bilan gaplashishda ham qo'l keladi.
- Intervyu savollari: "Imkonsiz holat nima, misol keltiring?", "Nega
isLoading+isErroro'rnigastatus?", "Immer qanday ishlaydi va nima uchun natijani muzlatadi?", "Holat mashinasini reducer bilan qanday yozasiz?".
Xulosa
- Chuqur holatni spread bilan yangilash qiyin;
producevauseImmerReducerqoralamani "o'zgartirish"ga imkon beradi, natija — yangi, muzlatilgan, tegilmagan qismlar — o'sha obyektlar. - Immer qoidasi: yo qoralamani o'zgartiring, yo yangi qiymat qaytaring; qoralama retseptdan tashqarida yashamaydi.
- n ta boolean — 2ⁿ kombinatsiya, ko'pi imkonsiz; bitta
statusva discriminated union ularni turning o'zida yo'qotadi. - Holat mashinasi — holatlar, hodisalar va ruxsat etilgan o'tishlar jadvali; reducer'da noma'lum o'tish — o'sha holat.
- Murakkab oqimlar uchun XState, tayyor UI mashinalari uchun Zag.
Keyingi dars: Context I: props drilling'siz ma'lumot uzatish — ma'lumotni daraxt bo'ylab oraliq komponentlarsiz yetkazamiz.
Manbalar
- React: "Extracting State Logic into a Reducer" — Immer bilan reducer bo'limi — react.dev/learn/extracting-state-logic-into-a-reducer
- React: "Choosing the State Structure" — imkonsiz holatlardan qochish — react.dev/learn/choosing-the-state-structure
- Immer 11.1.21 — immerjs.github.io/immer; use-immer 0.11.0 — github.com/immerjs/use-immer
- XState 5.33.2 — stately.ai/docs; Zag 1.46.0 — zagjs.com (npm versiyalari 2026-10-13 holati)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!