Vanilla JS + Vite โ€” state boshqarish โ€” oddiy store pattern

~25 daq ยท JavaScript ยท Vanilla JS + Vite

Muammodan boshlaylik

Internet do'kon: sarlavhada savat belgisi (๐Ÿ›’ 3), mahsulot kartochkalarida "Savatga" tugmasi, yon panelda savat ro'yxati, pastda jami summa.

function savatgaQosh(mahsulot) {
  savat.push(mahsulot);
  document.querySelector('#savat-soni').textContent = savat.length;
  savatRoyxatiniYangila();
  jaminiYangila();
  tugmaHolatiniYangila(mahsulot.id);
  localStorage.setItem('savat', JSON.stringify(savat));
}

Har bir yangi joy โ€” yangi qator. Bitta qatorni unutsangiz โ€” belgi 3, ro'yxatda esa 4 ta mahsulot. Va savatdan o'chirish funksiyasida hammasini yana takrorlash kerak.


Store pattern

G'oya:

  1. Holat (state) โ€” bitta joyda
  2. Holat faqat aniq funksiyalar orqali o'zgaradi
  3. O'zgarganda obunachilar avtomatik xabardor qilinadi
     harakat โ”€โ”€โ–ถ STORE (holat) โ”€โ”€โ–ถ obunachilar
       โ–ฒ                              โ”‚
       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ UI โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Bu โ€” Observer pattern (80-dars) va bir yo'nalishli ma'lumot oqimi.


Universal createStore

// src/lib/createStore.js
export function createStore(boshlangich) {
  let holat = boshlangich;
  const obunachilar = new Set();

  return {
    getState: () => holat,

    setState(yangilash) {
      const keyingi =
        typeof yangilash === 'function' ? yangilash(holat) : yangilash;
      if (Object.is(keyingi, holat)) return;
      holat = { ...holat, ...keyingi };
      obunachilar.forEach((fn) => fn(holat));
    },

    subscribe(fn) {
      obunachilar.add(fn);
      return () => obunachilar.delete(fn);
    },
  };
}

30 qator โ€” va bu Zustand kabi kutubxonalarning yuragi.


Savat store

// src/stores/savat.js
import { createStore } from '../lib/createStore.js';

const saqlangan = JSON.parse(localStorage.getItem('savat') ?? '[]');

export const savatStore = createStore({ mahsulotlar: saqlangan });

// harakatlar
export function qosh(mahsulot) {
  savatStore.setState((h) => {
    const bor = h.mahsulotlar.find((m) => m.id === mahsulot.id);
    return {
      mahsulotlar: bor
        ? h.mahsulotlar.map((m) => (m.id === mahsulot.id ? { ...m, soni: m.soni + 1 } : m))
        : [...h.mahsulotlar, { ...mahsulot, soni: 1 }],
    };
  });
}

export function ochir(id) {
  savatStore.setState((h) => ({
    mahsulotlar: h.mahsulotlar.filter((m) => m.id !== id),
  }));
}

// hosila qiymatlar (selector lar)
export const jamiSoni = (h) => h.mahsulotlar.reduce((s, m) => s + m.soni, 0);
export const jamiSumma = (h) => h.mahsulotlar.reduce((s, m) => s + m.narx * m.soni, 0);

// saqlash โ€” bitta obunachi
savatStore.subscribe((h) => {
  localStorage.setItem('savat', JSON.stringify(h.mahsulotlar));
});

UI qismlari

Har bir qism faqat o'zini biladi:

// savat belgisi
const belgi = document.querySelector('#savat-soni');
const chizBelgi = (h) => (belgi.textContent = jamiSoni(h));
chizBelgi(savatStore.getState());
savatStore.subscribe(chizBelgi);

// jami
const jamiEl = document.querySelector('#jami');
const chizJami = (h) => (jamiEl.textContent = `${jamiSumma(h).toLocaleString()} so'm`);
chizJami(savatStore.getState());
savatStore.subscribe(chizJami);

// tugmalar
document.addEventListener('click', (e) => {
  const tugma = e.target.closest('[data-savatga]');
  if (tugma) qosh(JSON.parse(tugma.dataset.savatga));
});

Endi qosh() UI haqida hech narsa bilmaydi. Yangi joy (masalan, mobil menyudagi belgi) qo'shish โ€” bitta subscribe.


Selector bilan obuna โ€” ortiqcha chizishni oldini olish

Savat belgisi faqat soni o'zgarganda yangilanishi kerak:

export function subscribeSelector(store, selector, fn) {
  let oldingi = selector(store.getState());
  fn(oldingi);
  return store.subscribe((h) => {
    const keyingi = selector(h);
    if (!Object.is(keyingi, oldingi)) {
      oldingi = keyingi;
      fn(keyingi);
    }
  });
}

subscribeSelector(savatStore, jamiSoni, (soni) => {
  belgi.textContent = soni;
});

Bir nechta store

src/stores/
โ”œโ”€โ”€ savat.js         // savat
โ”œโ”€โ”€ auth.js          // foydalanuvchi, token
โ””โ”€โ”€ ui.js            // tema, ochiq modal

Har bir sohaga alohida store โ€” kichik va tushunarli. Bir-biriga bog'liq bo'lsa, harakat funksiyasi ikkalasini chaqirishi mumkin:

import { authStore } from './auth.js';
import { savatStore } from './savat.js';

export function chiqish() {
  authStore.setState({ user: null, token: null });
  savatStore.setState({ mahsulotlar: [] });
}

[!WARNING] Obunachi ichida setState chaqirish cheksiz siklga olib kelishi mumkin: o'zgarish โ†’ obunachi โ†’ yana o'zgarish. Obunachilar holatni faqat o'qib, DOM yoki tashqi tizimni yangilashi kerak.

[!TIP] subscribe qaytaradigan funksiyani saqlang va komponent o'chirilganda chaqiring โ€” aks holda o'chirilgan elementlar uchun obunachilar xotirada qoladi (memory leak, 65-dars).


Qachon kutubxona kerak

Holat Tanlov
Kichik-o'rta vanilla loyiha O'z createStore ingiz โ€” yetarli
Devtools, middleware, persist kerak Zustand (zustand/vanilla โ€” framework siz ishlaydi)
Murakkab hodisalar, katta jamoa Redux Toolkit
Framework (React, Vue) Uning o'z vositalari + yuqoridagilar

Nima uchun shunday?

Nima uchun holatni markazlashtirish UI ni to'g'ridan-to'g'ri yangilashdan yaxshiroq?

Chunki "haqiqat manbai" bitta bo'ladi. To'g'ridan-to'g'ri yangilashda har bir harakat UI ning qaysi qismlari o'zgarishi kerakligini bilishi shart โ€” va bu bog'liqliklar ilova o'sgani sari kvadratik ko'payadi. Store pattern bu bog'liqlikni teskari aylantiradi: harakatlar faqat holatni o'zgartiradi, UI qismlari esa o'zlari holatga qarab chiziladi. Natijada UI hech qachon holatdan "orqada qolmaydi", yangi qism qo'shish boshqalarni o'zgartirishni talab qilmaydi, va har qanday vaqtda getState() orqali ilovaning to'liq holatini ko'rish mumkin.


Xulosa

Eslab qoling:

  • Store: holat bitta joyda, faqat harakatlar orqali o'zgaradi, obunachilar xabardor qilinadi
  • createStore โ€” getState, setState, subscribe (~30 qator)
  • Harakatlar UI ni bilmaydi; UI qismlari o'zi obuna bo'ladi
  • Selector lar โ€” hosila qiymatlar; subscribeSelector โ€” ortiqcha chizishsiz
  • Saqlash (localStorage) โ€” oddiy obunachi
  • Obunachida setState qilmang; obunani bekor qilishni unutmang

Keyingi bo'lim

80-dars: Design Patterns โ€” Singleton, Factory, Observer, Strategy