IlmHamroh
JavaScript Full-stack/19-qism. React: routing, server state, formalar28/36-dars20 daqiqa
Mundarija (22)

XState holat mashinalari: statechart, guard, invoke va useMachine bilan murakkab oqimlar

Qisqacha: Holat mashinasi — tizim bir vaqtda faqat bitta aniq holatda bo'ladigan va holatdan holatga faqat ruxsat etilgan hodisa bilan o'tadigan model: "imkonsiz holat" (bir vaqtda "yuborilmoqda" va "xato") yo'q. XState 5 uni statechart darajasiga ko'taradi: context (ma'lumot), guard (shartli o'tish), after (taymer), ichma-ich va parallel holatlar, invoke (promise yoki boshqa aktor). React'da useMachine(mashina) → [snapshot, send]. Kichik "store" uchun — @xstate/store, UI vidjetlari uchun — Zag. Kanon tajribasida XState +12.9 KB va 42 qator: oddiy ro'yxatga ortiqcha, murakkab oqimda — o'rinli.

Bu darsda

  • Jadval asosidagi kichik mashinani kutubxonasiz jonli yozasiz va "rad etilgan hodisa" ni ko'rasiz.
  • XState 5 da setup + createMachine bilan context, assign, guard va invoke li to'lov oqimini qurasiz.
  • Kechiktirilgan o'tish (after), ichma-ich va parallel holatlarni ishlatasiz.
  • React'da useMachine, snapshot.can() va snapshot.matches() bilan bosqichli formani boshqarasiz.
  • @xstate/store, Zag va Stately vizualizatorini taniysiz.
  • Holat mashinasi qachon kerak va qachon ortiqcha ekanini kanon tajribasi bilan ayta olasiz.

Oldin bilishingiz kerak: Reducer + Immer va holat mashinasi fikrlashi, Atom va proxy state, useReducer, Redux Toolkit II.

1. Nega bu kerak?

Reducer + Immer va holat mashinasi fikrlashi darsida bron formasini boolean'lardan bitta status ga o'tkazgan edik va XState bilan qisqa tanishgan edik. Endi «Bahor» saytida haqiqiy to'lov oqimi kerak:

  1. Savat bo'sh bo'lsa, "To'lash" ishlamasin.
  2. To'lov serverga ketadi; javob kutilayotganda tugma qayta bosilmasin.
  3. Karta rad etilsa — xato va «Qayta urinish», lekin ko'pi bilan 2 marta.
  4. Shu vaqtda oshxona buyurtmani tayyorlay boshlaydi — to'lovni kutmasdan. 5 daqiqa to'lanmasa — kassirga eslatma.

Sardor buni useState lar bilan yozdi: isPaying, hasError, retries, isCooking, isPaid, reminderTimer. Olti o'zgaruvchi — 2⁵ × taymer holatlari, ularning ko'pchiligi ma'nosiz ("to'lanmoqda" va "to'landi" bir vaqtda). Har yangi talab yangi if qo'shadi va eski if lar bilan to'qnashadi.

Hayotiy o'xshatish: metro turniketi. Unda bor-yo'g'i ikki holat — "yopiq" va "ochiq". Yopiqda karta tekkizsangiz — ochiladi; itarsangiz — hech narsa. Ochiqda o'tsangiz — yopiladi. Turniket "yarim ochiq" bo'lmaydi, ikkita karta tekkizish ikki kishini o'tkazmaydi. Qoidalar jadvali hamma holatni oldindan aytadi — holat mashinasi shu.

2. Mashina — jadval

2.1 Jonli model: o'tishlar jadvali

Chekli holat mashinasi (finite state machine) uch narsadan iborat: holatlar ro'yxati, boshlang'ich holat va o'tishlar: "shu holatda shu hodisa kelsa — shu holatga". Jadvalda yo'q juftlik — rad etilgan hodisa: holat o'zgarmaydi. Saytda XState yo'q, shuning uchun avval kutubxonasiz o'quv modeli — mashina oddiy obyekt, o'tish esa useReducer:

jsx
import { useReducer } from "react";

// O'quv modeli: o'tishlar jadvali (XState emas)
const machine = {
  initial: "bosh",
  states: {
    bosh: { ADD: "savatda" },
    savatda: { ADD: "savatda", PAY: "tolanmoqda" },
    tolanmoqda: { OK: "tolandi", FAIL: "xato" },
    xato: { PAY: "tolanmoqda" },
    tolandi: {},
  },
};

function transition(state, event) {
  const next = machine.states[state][event.type];
  if (next === undefined) {
    console.log(`rad etildi: ${state} + ${event.type}`);
    return state;
  }
  console.log(`${state} → ${next}`);
  return next;
}

export default function App() {
  const [state, send] = useReducer(transition, machine.initial);
  return (
    <>
      <p>Holat: {state}</p>
      <button onClick={() => send({ type: "ADD" })}>Osh</button>{" "}
      <button onClick={() => send({ type: "PAY" })}>To'lash</button>
    </>
  );
}

Konsolda:

text
rad etildi: bosh + PAY
bosh → savatda
savatda → tolanmoqda
rad etildi: tolanmoqda + ADD

Ekranda:

text
Holat: tolanmoqda
Osh To'lash

Ikki if yozmadik: "savat bo'sh bo'lsa to'lama" va "to'lov ketayotganda savatni o'zgartirma". Ikkalasi ham jadvalning o'zidan kelib chiqdi — bosh da PAY yo'q, tolanmoqda da ADD yo'q. Mashina tilida xato — "imkonsiz holat" emas, shunchaki "bu hodisa bu yerda ma'nosiz".

stateDiagram-v2
  [*] --> bosh
  bosh --> savatda: ADD
  savatda --> savatda: ADD
  savatda --> tolanmoqda: PAY
  tolanmoqda --> tolandi: OK
  tolanmoqda --> xato: FAIL
  xato --> tolanmoqda: PAY
  tolandi --> [*]

Diagramma jadvalning o'zi: har strelka — bitta yozuv. Strelka yo'q joyda hodisa rad etiladi. Bunday rasmni jadvaldan avtomatik chizish mumkin — «Vizualizator» bo'limida.

2.2 Model nimani bilmaydi

Jadval chiroyli, lekin talablarimizning yarmini qoplamaydi:

  • Ma'lumot. Savatdagi taomlar, urinishlar soni — holat nomi emas. Ularni qayerda saqlaymiz?
  • Shart. "Qayta urinish — faqat 2 martagacha": bir xil hodisa, bir xil holat, lekin ba'zan ruxsat, ba'zan yo'q.
  • Vaqt. "5 daqiqada to'lanmasa — eslatma": hodisani foydalanuvchi emas, vaqt yuboradi.
  • Async ish. To'lov so'rovi: holatga kirganda boshlanadi, chiqqanda bekor bo'lishi kerak.
  • Bir vaqtda ikki jarayon. Oshxona va to'lov bir-biridan mustaqil. Bitta jadvalda ularni birlashtirsak, holatlar ko'paytiriladi: 3 × 3 = 9 ta.

Bularning hammasini qo'lda qo'shish mumkin — yoki bularni o'z ichiga olgan statechart dan foydalanish mumkin.

Tekshirib ko'ring: Modelda xato holatida ADD yuborilsa nima bo'ladi? Bu to'g'rimi?

Javob

Rad etiladi: xato: { PAY: "tolanmoqda" } da ADD yo'q. To'g'rimi — biznes qarori. Mehmon to'lov rad etilgach savatni o'zgartirmoqchi bo'lishi tabiiy. Unda jadvalga ADD: "savatda" qo'shasiz. Mashinaning foydasi aynan shu: bu savol kod ichida yashirinmaydi, jadvalda ochiq turadi va uni Jasur aka bilan muhokama qilsa bo'ladi.

3. XState 5: statechart

3.1 setup, context, assign, guard va invoke

Statechart — 1987 yilda David Harel taklif qilgan kengaytirilgan holat mashinasi: kontekst (ma'lumot), shartli o'tishlar, ichma-ich va parallel holatlar, vaqt. XState — uni JavaScript'da amalga oshiradigan kutubxona. Kursda versiyalari: xstate 5.33.2, @xstate/react 6.1.0 (kanon tajribasi). XState 5 da mashina setup({...}).createMachine({...}) bilan yoziladi: setup ichida mashinada ishlatiladigan guard, amal va aktorlar nom bilan e'lon qilinadi.

«Nega bu kerak?» bo'limidagi 1–3 talablar bitta mashinada. Saytda XState yo'q — Node'da ishga tushiramiz:

js
import { assign, createActor, fromPromise, setup } from "xstate";

// soxta to'lov serveri: 1-urinish rad, 2-si muvaffaqiyat
let attempt = 0;
const payOnServer = fromPromise(async ({ input }) => {
  attempt += 1;
  await new Promise((r) => setTimeout(r, 20));
  if (attempt === 1) throw new Error("Karta rad etildi");
  return `chek-${input.total}`;
});

const checkoutMachine = setup({
  actors: { payOnServer },
  guards: {
    hasItems: ({ context }) => context.items.length > 0,
    canRetry: ({ context }) => context.retries < 2,
  },
}).createMachine({
  id: "buyurtma",
  initial: "savat",
  context: { items: [], retries: 0, receipt: null },
  states: {
    savat: {
      on: {
        ADD: {
          actions: assign({
            items: ({ context, event }) => [
              ...context.items,
              event.dish,
            ],
          }),
        },
        CHECKOUT: { guard: "hasItems", target: "tolov" },
      },
    },
    tolov: {
      invoke: {
        src: "payOnServer",
        input: ({ context }) => ({
          total: context.items.length * 1000,
        }),
        onDone: {
          target: "tasdiqlandi",
          actions: assign({ receipt: ({ event }) => event.output }),
        },
        onError: { target: "xato" },
      },
    },
    xato: {
      on: {
        RETRY: {
          guard: "canRetry",
          target: "tolov",
          actions: assign({
            retries: ({ context }) => context.retries + 1,
          }),
        },
      },
    },
    tasdiqlandi: { type: "final" },
  },
});

const actor = createActor(checkoutMachine);
actor.subscribe((s) => console.log("holat:", s.value));
actor.start();

actor.send({ type: "CHECKOUT" }); // savat bo'sh — guard rad etadi
actor.send({ type: "ADD", dish: "Osh" });
actor.send({ type: "CHECKOUT" });
await new Promise((r) => setTimeout(r, 50));
actor.send({ type: "RETRY" });
await new Promise((r) => setTimeout(r, 50));
const snap = actor.getSnapshot();
console.log(snap.status, snap.context.receipt, snap.context.retries);

Toza papkada xstate@5.33.2 bilan Node 24 da:

text
holat: savat
holat: savat
holat: savat
holat: tolov
holat: xato
holat: tolov
holat: tasdiqlandi
done chek-1000 1

Qatorma-qator:

  • context — mashinaning ma'lumoti: items, retries, receipt. Holat nomi (savat, tolov) "qayerdamiz" ni, kontekst "nima bor" ni aytadi.
  • assign — kontekstni yangilaydigan amal (action). U sof: eski kontekstdan yangisini hisoblaydi, reducer kabi.
  • Guard — o'tishga shart. CHECKOUT: { guard: "hasItems", target: "tolov" }: savat bo'sh — guard false — o'tish yo'q. Konsoldagi ikkinchi holat: savat — rad etilgan CHECKOUT (kuzatuvchi har hodisadan keyin snapshot oladi, holat o'sha). Uchinchisi — ADD (holat o'sha, kontekst yangi).
  • invoke — holatga kirganda ishga tushadigan va undan chiqqanda to'xtatiladigan aktor. fromPromise — promise'dan aktor. Natija — onDone (event.output), xato — onError. tolov holatidan chiqildimi — so'rov "kerak emas" deb hisoblanadi: race condition ning oldi olinadi.
  • Urinishlar: birinchi to'lov rad etildi → xato. RETRY — canRetry (retries < 2) — ruxsat, retries 1 bo'ldi, qayta tolov. Ikkinchi urinish o'tdi → tasdiqlandi, type: "final" → aktor status: done.

Aktor (actor) — XState'ning asosiy birligi: o'z holatiga ega, xabar (hodisa) oladi va boshqa aktorlarni yarata oladi. createActor(mashina) — mashinadan jonli nusxa; mashinaning o'zi — faqat tavsif (Jotai'dagi atom va store kabi).

3.2 Kechiktirilgan o'tish va parallel holatlar

To'rtinchi talab: oshxona va to'lov bir vaqtda, va to'lov kechiksa — eslatma. Parallel holat (type: "parallel") — bir vaqtda faol bo'lgan bir nechta mustaqil "hudud". Kechiktirilgan o'tish (after: { 100: "eslatma" }) — holatda shuncha ms qolinsa, o'tish o'zi bo'ladi; holatdan oldinroq chiqilsa, taymer bekor qilinadi. Misolda 5 daqiqa o'rniga 100 ms:

js
import { createActor, createMachine } from "xstate";

// buyurtma: oshxona va to'lov — PARALLEL, bir-birini kutmaydi
const orderMachine = createMachine({
  id: "buyurtma",
  type: "parallel",
  states: {
    oshxona: {
      initial: "tayyorlanmoqda",
      states: {
        tayyorlanmoqda: { on: { READY: "tayyor" } },
        tayyor: { type: "final" },
      },
    },
    tolov: {
      initial: "kutilmoqda",
      states: {
        // 100 ms da to'lanmasa — eslatma (kechiktirilgan o'tish)
        kutilmoqda: {
          on: { PAID: "tolandi" },
          after: { 100: "eslatma" },
        },
        eslatma: { on: { PAID: "tolandi" } },
        tolandi: { type: "final" },
      },
    },
  },
});

const actor = createActor(orderMachine);
actor.subscribe((s) => console.log(JSON.stringify(s.value)));
actor.start();

actor.send({ type: "READY" });
await new Promise((r) => setTimeout(r, 150));
actor.send({ type: "PAID" });
console.log("status:", actor.getSnapshot().status);

Node 24, xstate 5.33.2:

text
{"oshxona":"tayyorlanmoqda","tolov":"kutilmoqda"}
{"oshxona":"tayyor","tolov":"kutilmoqda"}
{"oshxona":"tayyor","tolov":"eslatma"}
{"tolov":"tolandi","oshxona":"tayyor"}
status: done
  • Holat qiymati endi obyekt: har hudud — o'z holati. Bir paytning o'zida oshxona: tayyor va tolov: kutilmoqda.
  • READY faqat oshxona hududini o'zgartirdi, to'lovga tegmadi.
  • 100 ms o'tdi — eslatma. Hech kim hodisa yubormadi: vaqtning o'zi hodisa.
  • Ikkala hudud ham final ga yetgach — butun mashina done.

Ichma-ich (hierarchical) holat — holat ichida holatlar: misoldagi oshxona va tolov ning har biri initial va states ga ega. Ota holatdagi on hamma bolalarga tegishli bo'ladi: masalan, tolov: { on: { CANCEL: "#buyurtma.bekor" }, states: {...} } — to'lovning qaysi bosqichida bo'lmasin, bekor qilish ishlaydi. Bitta qoida, uch joyga takrorlanmaydi.

stateDiagram-v2
  state buyurtma {
    state oshxona {
      [*] --> tayyorlanmoqda
      tayyorlanmoqda --> tayyor: READY
    }
    --
    state tolov {
      [*] --> kutilmoqda
      kutilmoqda --> eslatma: 100 ms
      kutilmoqda --> tolandi: PAID
      eslatma --> tolandi: PAID
    }
  }

Diagrammadagi uzuq chiziq (--) — parallel hududlar chegarasi. Bitta jadvalda yozilsa, 2 × 3 = 6 holat va 10 dan ortiq strelka bo'lardi; parallel holat bilan — ikki mustaqil kichik mashina.

Tekshirib ko'ring: Parallel misolda PAID 50 ms da (eslatmadan oldin) yuborilsa, konsolda eslatma chiqadimi?

Javob

Yo'q. PAID kutilmoqda dan tolandi ga o'tkazadi — holatdan chiqildi, after taymeri bekor qilinadi. Kechiktirilgan o'tish faqat holatda uzluksiz shuncha vaqt qolinganda ishlaydi. Qo'lda setTimeout bilan yozilganda buni unutish oson: taymer o'chirilmay qolsa, to'langan buyurtmaga eslatma ketadi.

4. React'da: useMachine

4.1 Bosqichli forma

React'da mashina useMachine bilan ulanadi: u aktorni komponent bilan birga yaratadi va to'xtatadi, [snapshot, send] qaytaradi — useReducer ga juda o'xshash. Stol bron qilishning uch qadamli formasi (@xstate/react 6.1.0, React 19.3.0, Chrome 155 — «Keyingi», «Orqaga», «Keyingi», «Keyingi», «Tasdiqlash» bosildi):

jsx
import { useMachine } from "@xstate/react";
import { createMachine } from "xstate";

const wizardMachine = createMachine({
  id: "bron",
  initial: "sana",
  states: {
    sana: { on: { NEXT: "mehmonlar" } },
    mehmonlar: { on: { NEXT: "tasdiq", BACK: "sana" } },
    tasdiq: { on: { CONFIRM: "tayyor", BACK: "mehmonlar" } },
    tayyor: { type: "final" },
  },
});

export default function App() {
  const [snapshot, send] = useMachine(wizardMachine);
  console.log("qadam:", snapshot.value);
  return (
    <>
      <p>Qadam: {snapshot.value}</p>
      <button
        disabled={!snapshot.can({ type: "BACK" })}
        onClick={() => send({ type: "BACK" })}
      >
        Orqaga
      </button>{" "}
      <button
        disabled={!snapshot.can({ type: "NEXT" })}
        onClick={() => send({ type: "NEXT" })}
      >
        Keyingi
      </button>{" "}
      {snapshot.matches("tasdiq") && (
        <button onClick={() => send({ type: "CONFIRM" })}>
          Tasdiqlash
        </button>
      )}
    </>
  );
}
text
qadam: sana
qadam: mehmonlar
qadam: sana
qadam: mehmonlar
qadam: tasdiq
qadam: tayyor

Ikki foydali metod:

  • snapshot.can(hodisa) — "bu hodisa hozir qabul qilinadimi?" Tugmaning disabled holati mashinadan olinadi: sana da «Orqaga» o'chiq, tasdiq da «Keyingi» o'chiq. Shart ikki joyda (mashina va JSX) yozilmaydi.
  • snapshot.matches(holat) — "hozir shu holatdamizmi?" (ichma-ich holatda ham: matches({ tolov: "eslatma" })).

Oxirgi holat tayyor — final: hech bir hodisa qabul qilinmaydi, ikkala tugma o'chiq.

Ikkita boshqa hook: useActor (tayyor aktor bilan) va useSelector(aktor, selektor) — aktor snapshot'idan faqat bir bo'lakni o'qish (Zustand I dagi selektor g'oyasi). Global mashina kerak bo'lsa (butun ilova uchun bitta), createActor modul darajasida yaratiladi va komponentlar useSelector bilan o'qiydi.

4.2 @xstate/store — mashinasiz kichik store

Har holat ham mashina emas. Savat — oddiy "ma'lumot + amallar". XState jamoasining @xstate/store paketi aynan shu uchun: Zustand'ga o'xshash kichik store, lekin hodisalar (send({ type })) bilan va XState vositalari bilan mos. Versiya — 4.2.3 (2026-08-10; 4.3.0 bir haftadan yangiroq bo'lgani uchun olinmadi):

js
import { createStore } from "@xstate/store";

const cartStore = createStore({
  context: { items: [], total: 0 },
  on: {
    added: (context, event) => ({
      items: [...context.items, event.name],
      total: context.total + event.price,
    }),
    cleared: () => ({ items: [], total: 0 }),
  },
});

cartStore.subscribe((snapshot) => {
  const { items, total } = snapshot.context;
  console.log(items, total);
});

cartStore.send({ type: "added", name: "Osh", price: 35000 });
cartStore.trigger.added({ name: "Choy", price: 5000 });
cartStore.trigger.cleared();

Node 24, @xstate/store 4.2.3:

text
[ 'Osh' ] 35000
[ 'Osh', 'Choy' ] 40000
[] 0

on dagi har funksiya — hodisa nomi va yangi kontekstni qaytaradigan "kichik reducer". cartStore.trigger.added({...}) — send({ type: "added", ... }) ning qisqa yozuvi (TypeScript'da hodisa turlari avtomatik). React'da — useSelector(cartStore, (s) => s.context.total); 4-versiyada u alohida paketda: @xstate/store-react (README bo'yicha o'rnatish — npm install @xstate/store-react, useSelector shu paketdan import qilinadi; 2.x versiyalari @xstate/store 4 ga tayanadi). Eski 3-versiyada u @xstate/store/react yo'lida edi — eski maqolalarda shuni ko'rasiz. Holat keyinchalik murakkablashsa (holatlar, guard'lar paydo bo'lsa), uni to'liq mashinaga ko'chirish oson — hodisa nomlari o'sha.

4.3 Zag va vizualizator

Zag (@zag-js/*) — holat mashinalari asosidagi UI vidjetlari: menyu, dialog, combobox, slayder, sana tanlagich. Ichida — har vidjetning tayyor mashinasi (ochiq/yopiq, fokus qayerda, klaviatura nima qiladi, ARIA atributlari). Siz faqat ko'rinishni chizasiz. Ark UI va Chakra UI 3 shu asosda qurilgan — UI kutubxonalar xaritasi da ko'ramiz. Headless komponentlar darsidagi "mantiq alohida, ko'rinish alohida" g'oyasining mashina bilan qilingani.

Vizualizator — Stately (XState mualliflari) ning stately.ai/viz sahifasi va VS Code kengaytmasi: mashina kodini joylashtirasiz, u diagramma chizadi va siz hodisalarni bosib, oqimni "o'ynatib" ko'rasiz. Teskarisi ham bor: diagrammani chizib, koddan generatsiya qilish. Biz ishlatgan mermaid stateDiagram — xuddi shu g'oyaning statik varianti.

Diqqat: vizualizatorga kod joylashtirish — uni uchinchi tomon saytiga yuborish. Ish loyihasida maxfiy mantiq bo'lsa, kompaniya qoidasini so'rang yoki VS Code kengaytmasining oflayn rejimidan foydalaning.

5. Kanon tajribasi: XState varianti (nega kanonga kirmadi)

Bu darsda vazifalar o'zgarmaydi. Tajriba — bir xil vazifa (ro'yxat: qo'shish, belgilash, filtr, "qoldi") yetti holat vositasi bilan (Redux Toolkit I va Jotai, Valtio, MobX darslaridagi grafiklar). XState varianti (aynan):

tsx
// holat-xstate.tsx — XState 5 + @xstate/react 6 (mashina:
// holatlar va hodisalar; bu oddiy ro'yxat uchun — bitta holat,
// context'da ma'lumot)
import { assign, setup } from "xstate";
import { useMachine } from "@xstate/react";
import { Korinish, ishgaTushir } from "./_umumiy.tsx";
import type { Filtr, Vazifa } from "./_umumiy.tsx";

const mashina = setup({
  types: {
    context: {} as { vazifalar: Vazifa[]; filtr: Filtr },
    events: {} as
      | { type: "qosh"; matn: string }
      | { type: "almashtir"; id: number }
      | { type: "filtr"; filtr: Filtr },
  },
}).createMachine({
  context: { vazifalar: [], filtr: "hammasi" },
  on: {
    qosh: {
      actions: assign({
        vazifalar: ({ context, event }) => [...context.vazifalar, { id: context.vazifalar.length + 1, matn: event.matn, bajarildi: false }],
      }),
    },
    almashtir: {
      actions: assign({
        vazifalar: ({ context, event }) => context.vazifalar.map((v) => (v.id === event.id ? { ...v, bajarildi: !v.bajarildi } : v)),
      }),
    },
    filtr: { actions: assign({ filtr: ({ event }) => event.filtr }) },
  },
});

function Ilova() {
  const [h, send] = useMachine(mashina);
  return (
    <Korinish
      holat={{
        ...h.context,
        qosh: (matn) => send({ type: "qosh", matn }),
        almashtir: (id) => send({ type: "almashtir", id }),
        filtrla: (filtr) => send({ type: "filtr", filtr }),
      }}
    />
  );
}
ishgaTushir(<Ilova />);

Natija: +12.9 KB gzip (eng og'ir) va 42 qator (eng uzun). Faylning o'z izohi aytganidek: "bu oddiy ro'yxat uchun — bitta holat, context'da ma'lumot". Mashinaning kuchi — holatlar va o'tishlar — bu vazifada umuman ishlatilmadi: states yo'q, faqat on + assign. Ya'ni XState bu yerda qimmat reducer bo'ldi.

Nega kanonga kirmadi: vazifalar dagi eng murakkab oqim — ko'p qadamli forma (/yangi, RHF III: wizard). U React Hook Form'ning trigger(maydonlar) va oddiy qadam raqami bilan yetarli bo'ldi: qadamlar chiziqli (oldinga/orqaga), parallel jarayon va taymer yo'q. O'chirish dialogi, tarmoq holati, Orqaga tugmasi — har biri 2–3 holatli va o'z joyida (holat mashinasi fikrlashi bilan status maydonlari).

Qachon XState — to'g'ri tanlov:

  • Ko'p bosqichli tarmoqlanuvchi oqim: to'lov, KYC (shaxsni tasdiqlash), bron, onboarding — guard'lar, qayta urinish, bekor qilish.
  • Vaqt muhim: taymerlar, "N soniyada javob bo'lmasa", qayta ulanish (WebSocket — Real-time).
  • Parallel jarayonlar: yuklash + tahrir + avtosaqlash; media pleyer (ijro, ovoz, to'liq ekran).
  • Holatlar biznes bilan muhokama qilinadi: diagramma — umumiy til, test esa har o'tishni tekshiradi.

6. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
hodisa yuborildi, hech narsa bo'lmadi joriy holatda bu hodisa yo'q (rad etildi) yoki guard false snapshot.can(hodisa); vizualizatorda tekshiring
context o'zgarmadi context.items.push(...) — to'g'ridan-to'g'ri o'zgartirish assign({ items: ({ context }) => [...] })
taymer to'langan buyurtmaga eslatma yubordi setTimeout qo'lda, holatdan chiqqanda tozalanmagan after: { ms: "holat" }
eski so'rov natijasi yangi holatni buzdi async ish holatga bog'lanmagan invoke + fromPromise (holatdan chiqilsa — e'tiborsiz)
holatlar soni portladi (9, 12, …) mustaqil jarayonlar bitta jadvalda type: "parallel"
guard nomi bo'yicha xato setup({ guards }) da e'lon qilinmagan nomli guard/amallar — setup ichida
oddiy savatga mashina — ko'p kod holat yo'q, faqat ma'lumot @xstate/store, Zustand yoki useReducer

7. Mashqlar

Mashqlar kurs/mashqlar/19/28-xstate/ da. 2-mashq — XState bilan Node'da (o'z papkangizda xstate@5.33.2, .mjs fayl); 3-mashq — saytda.

1-mashq (oson): jadvalni o'qing

«Jonli model» bo'limidagi machine jadvali bo'yicha javob bering:

  • savatda holatida OK keldi → holat: (rad etildi)
  • tolanmoqda holatida FAIL keldi → holat:
  • xato holatida PAY keldi → holat:
  • tolandi holatida qabul qilinadigan hodisalar soni:
Yechim

OK faqat tolanmoqda da ma'noli — savatda da rad etiladi va holat o'sha qoladi. Keyin FAIL → xato, undan PAY → yana tolanmoqda (qayta urinish). tolandi: {} — bo'sh: yakuniy holat, hech bir hodisa qabul qilinmaydi (XState'da — type: "final").

2-mashq (o'rta): stol broni guard bilan

XState bilan stol mashinasi: bosh → BOOK (guard: mehmonlar ≤ 6, kontekstga guests yoziladi) → band → FREE → bosh (guests 0). Ketma-ketlik: 8 kishilik bron (rad), 4 kishilik (qabul), 2 kishilik (band stolga — rad), bo'shatish. Har hodisadan keyin holat va mehmonlar sonini chiqaring.

Ishora: hodisaga qarab guard — ({ event }) => event.guests <= 6.

Yechim
js
import { assign, createActor, setup } from "xstate";

const tableMachine = setup({
  guards: {
    fits: ({ event }) => event.guests <= 6,
  },
}).createMachine({
  id: "stol",
  initial: "bosh",
  context: { guests: 0 },
  states: {
    bosh: {
      on: {
        BOOK: {
          guard: "fits",
          target: "band",
          actions: assign({ guests: ({ event }) => event.guests }),
        },
      },
    },
    band: {
      on: {
        FREE: {
          target: "bosh",
          actions: assign({ guests: 0 }),
        },
      },
    },
  },
});

const actor = createActor(tableMachine).start();
const show = () => {
  const { value, context } = actor.getSnapshot();
  console.log(value, context.guests);
};

actor.send({ type: "BOOK", guests: 8 });
show();
actor.send({ type: "BOOK", guests: 4 });
show();
actor.send({ type: "BOOK", guests: 2 });
show();
actor.send({ type: "FREE" });
show();

Node 24, xstate 5.33.2:

text
bosh 0
band 4
band 4
bosh 0

Ikki xil rad etish: 8 kishi — bosh da BOOK bor, lekin guard false; 2 kishi — band da BOOK umuman yo'q. Ikkalasida ham holat va kontekst o'zgarmadi — "band stolga ustma-ust bron" kabi xato tug'ilmadi.

3-mashq (qiyin): kutubxonasiz after

O'quv modeliga kechiktirilgan o'tish qo'shing. Mashina: kutilmoqda → (PAID) → tolandi; kutilmoqda da 200 ms qolinsa → eslatma; eslatma → (PAID) → tolandi. Talab: taymer faqat kutilmoqda da ishlasin va holatdan chiqilganda tozalansin.

Ishora: useEffect bog'liqligi — joriy holat; effekt kutilmoqda da setTimeout qo'yadi va cleanup da clearTimeout.

Yechim
jsx
import { useEffect, useReducer } from "react";

const machine = {
  kutilmoqda: { PAID: "tolandi", TIMEOUT: "eslatma" },
  eslatma: { PAID: "tolandi" },
  tolandi: {},
};

const after = { kutilmoqda: { ms: 200, event: "TIMEOUT" } };

function transition(state, event) {
  const next = machine[state][event.type] ?? state;
  console.log(`${state} + ${event.type} → ${next}`);
  return next;
}

export default function App() {
  const [state, send] = useReducer(transition, "kutilmoqda");

  useEffect(() => {
    const timer = after[state];
    if (timer === undefined) return;
    const id = setTimeout(
      () => send({ type: timer.event }),
      timer.ms,
    );
    // holatdan chiqildi — taymer yo'q
    return () => clearTimeout(id);
  }, [state]);

  return (
    <>
      <p>To'lov: {state}</p>
      <button onClick={() => send({ type: "PAID" })}>To'landi</button>
    </>
  );
}

Konsolda:

text
kutilmoqda + TIMEOUT → eslatma
eslatma + PAID → tolandi

Ekranda:

text
To'lov: tolandi
To'landi

Agar «To'landi» 200 ms dan oldin bosilsa: kutilmoqda + PAID → tolandi, keyin effekt tozalash funksiyasi taymerni o'chiradi — TIMEOUT hech qachon kelmaydi. Shu "tozalashni unutmaslik" XState'da after ichiga qurilgan.

4-mashq (tahlil): mashina kerakmi?

Har vazifa uchun ayting: XState, @xstate/store, useReducer + status yoki oddiy useState?

  1. Tugma bosilganda ochilib-yopiladigan yon panel.
  2. Savat: qo'shish, o'chirish, jami summa.
  3. Kuryer ilovasi: buyurtma qabul → oshxonada → yo'lda → yetkazildi; har bosqichda bekor qilish; 10 daqiqa javobsiz — boshqa kuryerga; GPS va chat bir vaqtda ishlaydi.
Yechim
  1. useState (open: boolean) — ikki holat, bitta hodisa.
  2. @xstate/store, Zustand yoki useReducer — holatlar yo'q, ma'lumot va amallar bor. XState mashinasi bu yerda tajribadagi "qimmat reducer" bo'lardi.
  3. XState — ketma-ket bosqichlar, ota holatdagi "bekor qilish" (ichma-ich), taymer (after), parallel hududlar (GPS, chat), server so'rovlari (invoke). Aynan statechart uchun vazifa.

8. Real ishda

  • To'lov va onboarding oqimlari — XState'ning eng ko'p uchraydigan joyi (bank, fintech, telekom). Diagramma talablar hujjatining bir qismi bo'ladi.
  • UI kutubxonalari ichida — Zag (Ark UI, Chakra UI 3): siz mashina yozmasangiz ham, uni ishlatasiz.
  • Test: mashina sof funksiyaga yaqin — har holat va o'tishni React'siz, Node'da tekshirish oson (bugungi misollar kabi). @xstate/graph barcha yo'llarni avtomatik sanab, test ssenariylarini yasaydi.
  • Backend'da ham: XState aktorlari Node'da ishlaydi (navbat ishlovchilari, uzoq jarayonlar).
  • Intervyu savollari: "holat mashinasi nima va qanday muammoni hal qiladi?", "imkonsiz holat nima?", "statechart oddiy mashinadan nimasi bilan farq qiladi?", "guard va action farqi?", "qachon XState ortiqcha?".

Xulosa

  • Holat mashinasi: aniq holatlar, boshlang'ich holat va o'tishlar jadvali; jadvalda yo'q hodisa — rad etiladi, imkonsiz holat tug'ilmaydi.
  • XState 5: setup (nomli guard, amal, aktorlar) + createMachine; context va assign — ma'lumot; guard — shartli o'tish.
  • after — vaqt hodisasi (holatdan chiqilsa bekor), type: "parallel" — mustaqil hududlar, ichma-ich holat — umumiy qoidalar ota holatda; invoke + fromPromise — holatga bog'langan async ish.
  • React'da useMachine → [snapshot, send]; snapshot.can() va matches() — tugmalar va ko'rinish mashinadan.
  • @xstate/store — mashinasiz kichik store; Zag — mashinali UI vidjetlari; Stately — vizualizator.
  • Tajriba: oddiy ro'yxatda XState +12.9 KB va 42 qator — ortiqcha; vazifalar wizard'i RHF qadamlari bilan yetdi. XState — tarmoqlanuvchi, vaqtli, parallel oqimlar uchun.

Keyingi dars: React Hook Form I: useForm, register va xatolar — formalarni uncontrolled inputlar va kam render bilan qurish; vazifalar da tahrir formasi.

Manbalar

  • XState v5: Machines, setup, Guards, Delayed transitions, Parallel states, Invoke — stately.ai/docs (2026-10 holatiga)
  • @xstate/react: useMachine, useSelector; @xstate/store — stately.ai/docs
  • Zag — zagjs.com; Stately visualizer — stately.ai/viz
  • D. Harel, "Statecharts: A Visual Formalism for Complex Systems" (1987)
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
XState holat mashinalari: statechart, guard, invoke va useMachine bilan murakkab oqimlar — IlmHamroh