Vanilla JS + Vite โ state boshqarish โ oddiy store pattern
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:
- Holat (state) โ bitta joyda
- Holat faqat aniq funksiyalar orqali o'zgaradi
- 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
setStatechaqirish cheksiz siklga olib kelishi mumkin: o'zgarish โ obunachi โ yana o'zgarish. Obunachilar holatni faqat o'qib, DOM yoki tashqi tizimni yangilashi kerak.
[!TIP]
subscribeqaytaradigan 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
setStateqilmang; obunani bekor qilishni unutmang
Keyingi bo'lim
80-dars: Design Patterns โ Singleton, Factory, Observer, Strategy