Series: JavaScript Basics Lesson 80

State Management Asoslari

State management'ning asosiy g'oyasi (Single Source of Truth), Module Pattern + Pub/Sub + Factory'ning createStore ichida birlashishi, setState'ning to'g'ridan-to'g'ri mutatsiyadan nega yaxshiligi (reference equality), Redux'ning reducer pattern'i, va bu yondashuvning V8'dagi GC/tezlik oqibatlari.

80-dars

State Management Asoslari

Kichik dasturda state (holat) — bu shunchaki bir nechta o'zgaruvchi:

let count = 0;
let user = null;
let isLoading = false;

Lekin dastur kattalashganda, muammo paydo bo'ladi: bitta stateга (masalan, user) ko'plab, bir-biridan uzoq joylashgan komponentlar murojaat qiladi va uni o'zgartiradi. Kim, qachon, nima uchun o'zgartirganini kuzatib borish qiyinlashadi:

// Header.js
user.name = 'Yangi ism'; // bu yerda o'zgardi

// Sidebar.js
user.role = 'admin'; // bu yerda ham o'zgardi

// ProfilePage.js
delete user.email; // bu yerda umuman o'chirib yuborildi

Har bir joy user obyektiga to'g'ridan-to'g'ri, nazoratsiz tegishi mumkin. Agar biror joyda bug chiqsa (user.email kutilmaganda undefined), qaysi fayl buni qilgani noma'lum — chunki o'zgartirish huquqi hech qanday markazlashtirilgan nazoratsiz, hamma joyga tarqalib ketgan.

State management — bu muammoni hal qilish uchun state'ni bitta markazga yig'ish va unga faqat belgilangan yo'l orqali (funksiya chaqiruvi orqali, to'g'ridan-to'g'ri emas) tegish qoidasini o'rnatish.


1. Asosiy g'oya — bitta manba (Single Source of Truth)

function createStore(initialState) {
  let state = initialState;
  const listeners = []; // Pub/Sub'dagi "subscribers" — tanish, shunday emasmi?

  function getState() {
    return state;
  }

  function setState(newState) {
    state = { ...state, ...newState }; // Shallow clone!
    listeners.forEach(listener => listener(state)); // hammaga xabar
  }

  function subscribe(listener) {
    listeners.push(listener);
    return () => { // unsubscribe funksiyasini qaytarish - qulay pattern
      const index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  }

  return { getState, setState, subscribe };
}

const store = createStore({ user: null, isLoading: false });

// Har qanday komponent faqat SHU orqali o'qiydi/yozadi
store.subscribe((state) => console.log('Yangi state:', state));

store.setState({ isLoading: true });
// Yangi state: { user: null, isLoading: true }

Diqqat qiling — bu tuzilma tanish tuyulishi kerak: bu aynan Module Pattern (closure orqali state va listenersni private saqlash) + Pub/Sub (subscribe/notify mexanizmi) + Factory (createStore — store yaratuvchi funksiya) — uchala oldingi mavzuning birlashmasi. State management kutubxonalari (Redux, Zustand) — tub mohiyatan aynan shu g'oyaning kengaytirilgan, qat'iyroq qoidali versiyasi.


2. Nega to'g'ridan-to'g'ri o'zgartirish emas, setState orqali — bu qat'iy qoida

// ❌ YOMON - to'g'ridan-to'g'ri mutatsiya
state.user = newUser;

// ✅ YAXSHI - setState orqali
setState({ user: newUser });

Sabab faqat "tartib" emas — bu yerda texnik sabab ham bor:

  1. Listener'lar xabardor bo'ladi — agar siz state.user = newUser deb to'g'ridan-to'g'ri yozsangiz, hech kim (hech qanday UI) bu o'zgarishni bilmaydi, chunki listeners.forEach(...) chaqirilmagan. UI yangilanmay qoladi.
  2. Referens taqqoslash (Object.is/===) ishlab qoladi — bu React kabi kutubxonalarda muhim optimallashtirish bilan bog'liq:
const oldState = { count: 0 };
oldState.count = 1; // to'g'ridan-to'g'ri o'zgartirildi
// oldState === oldState  →  true (bu SHU BIR XIL obyekt, garchi ichi o'zgargan bo'lsa ham!)
const oldState = { count: 0 };
const newState = { ...oldState, count: 1 }; // yangi obyekt yaratildi (immutability!)
// oldState === newState  →  false (ikkita TURLI obyekt)

React (va boshqa framework'lar) ichki tekshiruvda oldState === newStateni solishtiradi (bu — reference equality, oddiy pointer taqqoslash, juda tez operatsiya). Agar siz state'ni to'g'ridan-to'g'ri mutatsiya qilsangiz, oldState va newState bir xil obyektga ishora qiladi (=== natijasi true), va React "hech narsa o'zgarmagan" deb noto'g'ri xulosa chiqarib, qayta render qilmaydi — bu klassik "state o'zgardi lekin UI yangilanmadi" bugining sababi.

Bu — avvalda o'rgangan Immutability mavzusining aynan shu yerdagi amaliy zaruriyati: yangi state — har doim yangi obyekt ({ ...oldState, ...changes }) bo'lishi kerak, aynan shu shallow copy orqali === taqqoslash tez va ishonchli ishlashi uchun.


3. Reducer pattern — Redux'ning asosiy g'oyasi

Yuqoridagi setState — istalgan narsani to'g'ridan-to'g'ri o'zgartirish imkonini beradi, bu hali ham nazoratsiz. Redux bir qadam oldinga o'tib, faqat maxsus "action" obyektlar orqali o'zgarishga ruxsat beradi:

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'SET_USER':
      return { ...state, user: action.payload };
    default:
      return state; // noma'lum action - state O'ZGARMASDAN qaytariladi
  }
}

function createStore(reducer, initialState) {
  let state = initialState;
  const listeners = [];

  function dispatch(action) {
    state = reducer(state, action); // YAGONA joy - state shu yerda o'zgaradi
    listeners.forEach(fn => fn(state));
  }

  function getState() { return state; }
  function subscribe(fn) { listeners.push(fn); }

  return { dispatch, getState, subscribe };
}

const store = createStore(reducer, { count: 0, user: null });

store.dispatch({ type: 'INCREMENT' });
console.log(store.getState()); // { count: 1, user: null }

reducer — bu pure function: bir xil state + action kiritilsa, har doim bir xil natija qaytaradi, va hech qanday tashqi narsani o'zgartirmaydi (side-effect'siz). Bu xususiyat tufayli:

  • Debug qilish oson — har bir o'zgarish action.type orqali loglanishi mumkin ("kim, qachon, nima uchun o'zgartirdi" degan savolga aniq javob).
  • Time-travel debugging — har bir action'ni tartib bilan saqlab, orqaga/oldinga qayta o'ynash mumkin (Redux DevTools aynan shunday ishlaydi) — bu faqat reducer pure bo'lgani uchun mumkin.

4. V8/runtime darajasida — bu nimaga bog'liq

Bu mavzu ham (Design Patterns kabi) V8'ning maxsus mexanizmi emas — bu arxitektura qarori, oddiy closure, obyekt spread, va funksiya chaqiruvlari ustida qurilgan. Lekin ikkita amaliy bog'liqlik bor:

  1. Shallow copy narxi — { ...state, count: 1 } har safar yangi obyekt yaratadi. Katta, chuqur nested state uchun bu har bir dispatchda yangi obyekt yaratish degani — agar state juda katta bo'lsa (masalan, minglab elementli massiv), bu xotira va GC bosimini oshirishi mumkin (Garbage Collection — ko'p vaqtinchalik obyekt yaratilishi Minor GC'ni tezroq ishga tushiradi, chunki Young Generation tezroq to'ladi).
  2. Reference equality tezligi — yuqorida aytganimizdek, oldState === newState — bu pointer taqqoslash, O(1) operatsiya, obyekt ichidagi hamma property'ni solishtirishdan (deep equality, O(n)) ancha tezroq. Shuning uchun immutable pattern ataylab shunday qurilgan — tezlik uchun chuqur emas, sayoz (shallow) taqqoslash yetarli bo'lishi uchun.

Real kutubxonalar — bir xil g'oyaning turli ko'rinishlari

Kutubxona Yondashuv
Redux Reducer + Action + bitta global store (yuqoridagi misolning to'liq versiyasi)
Zustand Xuddi shunga o'xshash, lekin kamroq boilerplate, createStorega yaqinroq
MobX Boshqacha falsafa — mutatsiyaga ruxsat beradi, lekin Proxy orqali kuzatadi (qaysi property o'qilgan/yozilgan — avtomatik "reactive" qiladi)
React Context + useState Kichik loyihalar uchun — o'ziga xos mini-store, lekin katta loyihada re-render muammolari chiqishi mumkin