IlmHamroh
JavaScript Full-stack/19-qism. React: routing, server state, formalar23/36-dars22 daqiqa
Mundarija (24)

Redux Toolkit: configureStore, createSlice va useSelector bilan global holat

Qisqacha: Redux — butun ilova holati bitta store'da, u faqat amal (action) yuborish (dispatch) orqali o'zgaradi, yangi holatni esa reducer hisoblaydi. Redux Toolkit (RTK) — Redux'ning rasmiy, zamonaviy yozilishi: createSlice reducer va amal yasovchilarni birga beradi (ichida Immer — state.items.push(x) deb yozasiz), configureStore store'ni tayyor tekshiruvlar bilan yaratadi. React'da — <Provider store>, useSelector (o'qish) va useDispatch (yuborish); TypeScript'da — useSelector.withTypes<RootState>().

Bu darsda

  • Flux oqimini (amal → reducer → yangi holat → ekran) chizib tushuntira olasiz.
  • createSlice bilan slice yozasiz va u qaytaradigan amal yasovchilarni ishlatasiz.
  • configureStore qo'shadigan ikki tekshiruvni (o'zgartirish va serializatsiya) ko'rasiz.
  • React'da Provider, useSelector, useDispatch bilan ishlaysiz va selektor tuzog'ini taniysiz.
  • TypeScript'da tiplangan hooklarni (withTypes) yasaysiz.
  • Redux qaysi loyihada to'g'ri tanlov ekanini va nega vazifalar da Zustand qolganini o'lchov bilan bilasiz.

Oldin bilishingiz kerak: useReducer, Reducer + Immer, Zustand I, Zustand II.

1. Nega bu kerak?

Zustand bilan «Bahor» savatini bir necha qatorda qurdik. Shunday ekan, nega yana bitta kutubxona?

Sardor ishga kirgan katta kompaniyada — bank ilovasi, 40 dasturchi, 6 yillik kod — holat Redux'da. U yerda hech kim "Zustand'ga ko'chamiz" demaydi: yuzlab ekran, minglab test shu kodga tayangan. Ish e'lonlarida ham "Redux" so'zi hamon tez-tez uchraydi. Ya'ni Redux'ni o'qiy va yoza olish — kasbiy zarurat.

Ikkinchi sabab — Redux'ning qat'iy tartibi. Kichik loyihada u ortiqcha qog'ozbozlikday tuyuladi. Katta jamoada esa aynan shu tartib foydali: holat qayerda o'zgaradi — doim reducer'da; nima o'zgartirdi — doim nomli amal; tarix — DevTools'da qadamma-qadam.

Hayotiy o'xshatish: davlat idorasidagi ariza. Hujjatni to'g'ridan-to'g'ri o'zgartirib bo'lmaydi. Ariza yozasiz ("amal": nima bo'lsin), uni mas'ul xodim ko'rib chiqadi ("reducer": qoida bo'yicha yangi hujjat chiqaradi), arxivda har ariza raqami bilan saqlanadi. Sekinroq, lekin "kim, qachon, nimani o'zgartirdi?" savoliga doim javob bor.

2. Flux oqimi

2.1 Uch qoida

Redux — 2015 yilda chiqqan holat boshqaruv kutubxonasi. U Flux g'oyasiga asoslangan — Facebook taklif qilgan bir tomonlama ma'lumot oqimi. Uch qoida:

  1. Yagona haqiqat manbai. Butun global holat bitta store'da, bitta obyekt daraxtida.
  2. Holat faqat o'qish uchun. Uni o'zgartirishning yagona yo'li — amal yuborish: dispatch({ type: "savat/added", payload: "Osh" }).
  3. O'zgarishni sof funksiya hisoblaydi. Reducer: (eskiHolat, amal) => yangiHolat, hech narsani joyida o'zgartirmaydi.

Bu qoidalar sizga tanish: useReducer darsida xuddi shunday yozgan edik — reducer, dispatch, amal (action), payload. Redux — o'sha reducer, faqat bitta komponentning emas, butun ilovaning holati uchun.

flowchart LR
  V["Ekran: tugma bosildi"] -->|"dispatch(amal)"| R["reducer(holat, amal)"]
  R --> S["store: yangi holat"]
  S -->|"obunachilarga xabar"| SEL["useSelector: kerakli qism"]
  SEL -->|"o'zgargan bo'lsa"| V

Diagrammadagi aylana doim bir tomonga aylanadi. Komponent holatni hech qachon to'g'ridan-to'g'ri yozmaydi — faqat amal yuboradi va natijani selektor orqali o'qiydi.

2.2 Jonli model: Redux 20 qatorda

Redux Toolkit saytdagi natija oynasida yo'q (u yerda faqat React va 19-qism kanoni kutubxonalari bor). G'oyani jonli ko'rish uchun kutubxonasiz kichik o'quv modeli yozamiz. Bu Redux emas, lekin uning yuragi aynan shunday ishlaydi:

jsx
import { useSyncExternalStore } from "react";

// O'quv modeli: Redux g'oyasi kutubxonasiz (Redux emas!)
function createStore(reducer) {
  let state = reducer(undefined, { type: "@@boshlash" });
  const listeners = new Set();
  return {
    getState: () => state,
    dispatch(action) {
      console.log("amal:", action.type, action.payload);
      state = reducer(state, action);
      listeners.forEach((listener) => listener());
    },
    subscribe(listener) {
      listeners.add(listener);
      return () => listeners.delete(listener);
    },
  };
}

function cartReducer(state = { items: [] }, action) {
  switch (action.type) {
    case "savat/added":
      return { items: [...state.items, action.payload] };
    default:
      return state;
  }
}

const store = createStore(cartReducer);

function useSelector(selector) {
  return useSyncExternalStore(store.subscribe, () =>
    selector(store.getState()),
  );
}

export default function App() {
  const items = useSelector((state) => state.items);
  const add = (name) =>
    store.dispatch({ type: "savat/added", payload: name });
  return (
    <>
      <button onClick={() => add("Osh")}>Osh</button>{" "}
      <button onClick={() => add("Manti")}>Manti</button>
      <p>Savat: {items.join(", ")}</p>
    </>
  );
}

Konsolda:

text
amal: savat/added Osh
amal: savat/added Manti

Ekranda:

text
Osh Manti
Savat: Osh, Manti

Uch narsaga qarang:

  1. reducer(undefined, { type: "@@boshlash" }) — store boshlang'ich holatni reducer'ning o'zidan oladi: state = { items: [] } (standart parametr, funksiya parametrlari). Haqiqiy Redux ham shunday qiladi, faqat ichki amal nomi boshqa.
  2. dispatch — yagona eshik: hamma o'zgarish shu yerdan o'tadi. Shuning uchun bitta console.log butun ilovaning tarixini yozdi. Redux DevTools aynan shu nuqtaga ulanadi.
  3. useSelector — useSyncExternalStore ustidagi yupqa qatlam, Zustand'dagidek.

Zustand'dan farqi: Zustand'da amal — store'dagi funksiya (add("Osh")), Redux'da — obyekt ({ type, payload }), uni reducer talqin qiladi. Obyekt bo'lgani uchun amalni yozib qo'yish, tarmoq orqali jo'natish, qayta o'ynatish (time travel) mumkin.

Tekshirib ko'ring: Modelda dispatch({ type: "savat/ochirildi" }) chaqirilsa nima bo'ladi? Ekran yangilanadimi?

Javob

Reducer bu turni bilmaydi — default tarmog'i eski holatni o'sha havola bilan qaytaradi. Obunachilar baribir chaqiriladi (model listeners.forEach ni har safar bajaradi), lekin selektor eski items havolasini qaytaradi va React Object.is bo'yicha "o'zgarmadi" deb chizmaydi. Konsolga esa amal: savat/ochirildi undefined chiqadi. Noma'lum amal — xato emas: Redux'da har reducer hamma amallarni oladi va o'ziga tegishlisini tanlaydi.

3. Redux Toolkit: createSlice va configureStore

3.1 Nega "Toolkit"?

Eski Redux kodida bitta savat uchun uch fayl yozilardi: amal turlari (const ADD = "ADD"), amal yasovchilar (function add(name) { return { type: ADD, payload: name } }) va spread'li reducer. Bu "boilerplate" Redux'ning eng ko'p tanqid qilingan tomoni edi. Redux Toolkit (RTK) — Redux jamoasining rasmiy javobi va bugun Redux yozishning tavsiya etilgan yagona usuli. Kursda versiyasi 2.13.0, React bog'lamasi react-redux 9.3.0 (kanon tajribasi, tajriba/package.json).

bash
npm i --save-exact @reduxjs/toolkit@2.13.0 react-redux@9.3.0

3.2 createSlice — reducer va amallar birga

Slice — Redux'da holatning bitta bo'lagi: nomi, boshlang'ich qiymati va reducerlari. Zustand II dagi slice g'oyasi bilan bir xil:

js
import { configureStore, createSlice } from "@reduxjs/toolkit";

const cartSlice = createSlice({
  name: "savat",
  initialState: { items: [] },
  reducers: {
    added(state, action) {
      state.items.push(action.payload);
    },
    cleared(state) {
      state.items = [];
    },
  },
});

const { added, cleared } = cartSlice.actions;
console.log(added("Osh"));
console.log(added.type);

const store = configureStore({
  reducer: { savat: cartSlice.reducer },
});

store.subscribe(() => console.log("obunachi:", store.getState()));

store.dispatch(added("Osh"));
store.dispatch(added("Manti"));
store.dispatch(cleared());

Toza papkada @reduxjs/toolkit@2.13.0 bilan Node 24 da ishga tushirdik:

text
{ type: 'savat/added', payload: 'Osh' }
savat/added
obunachi: { savat: { items: [ 'Osh' ] } }
obunachi: { savat: { items: [ 'Osh', 'Manti' ] } }
obunachi: { savat: { items: [] } }

Qatorma-qator:

  • cartSlice.actions.added — amal yasovchi (action creator): added("Osh") tayyor obyekt qaytaradi. Uni qo'lda yozmaysiz.
  • Amal turi — "savat/added": slice nomi + reducer nomi. Nom to'qnashuvi bo'lmaydi: boshqa slice'dagi added — "buyurtma/added".
  • configureStore({ reducer: { savat: … } }) — har slice holat daraxtida o'z kalitida: state.savat. Katta ilovada: { savat, menyu, foydalanuvchi }.
  • store.subscribe — Zustand'dagidek kuzatuvchi.

Amal nomlarini o'tgan zamonda yozish — Redux hujjatining tavsiyasi: added, cleared ("nima bo'ldi"), add, clear ("nima qil") emas. Amal — buyruq emas, sodir bo'lgan voqea. Bir voqeaga bir nechta slice javob berishi mumkin (keyingi darsda extraReducers).

3.3 Immer: "o'zgartirib" yozish va muzlatilgan natija

state.items.push(…) — bu Reducer + Immer darsidagi qoralama (draft). createSlice reducerlarni Immer bilan o'raydi: siz qoralamani "o'zgartirasiz", Immer yangi o'zgarmas holat yasaydi. Tekshirib ko'ramiz:

js
import { configureStore, createSlice } from "@reduxjs/toolkit";
const cartSlice = createSlice({
  name: "savat",
  initialState: { items: [] },
  reducers: {
    added(state, action) {
      state.items.push(action.payload);
    },
  },
});
const store = configureStore({
  reducer: { savat: cartSlice.reducer },
});
store.dispatch(cartSlice.actions.added("Osh"));
const before = store.getState();
store.dispatch(cartSlice.actions.added("Manti"));
console.log(before.savat.items, store.getState().savat.items);
console.log(Object.isFrozen(store.getState().savat.items));
store.getState().savat.items.push("Lag'mon");

Node 24, RTK 2.13.0:

text
[ 'Osh' ] [ 'Osh', 'Manti' ]
true
TypeError: Cannot add property 2, object is not extensible

Eski holat o'zgarmagan, yangisi — alohida massiv. Uchinchi qator — eng foydali joyi: holat muzlatilgan. Reducerdan tashqarida push qilsangiz, jim buzilish o'rniga darhol xato: "2-xususiyatni qo'shib bo'lmaydi, obyekt kengaytirilmaydi". Xato reducer qoidasini buzgan qatorni o'zi ko'rsatadi.

Reducer ichida ikki yo'ldan bittasi: qoralamani o'zgartiring yoki yangi qiymat qaytaring. cleared(state) { return { items: [] }; } ham to'g'ri. Lekin state = { items: [] } — xato: bu faqat mahalliy o'zgaruvchini almashtiradi, Immer buni ko'rmaydi.

3.4 configureStore tekshiruvlari

configureStore dev rejimida ikki middleware qo'shadi (Zustand II dagi g'oya: dispatch ni o'rab, qo'shimcha ish qiladi):

  1. Immutability check — holat ikki dispatch orasida joyida o'zgartirilganini ushlaydi.
  2. Serializability check — amal yoki holatda JSON'ga aylanmaydigan qiymat (Date, Map, funksiya, klass nusxasi) borligini ushlaydi.

Ikkinchisini sinab ko'ramiz — buyurtma vaqtini Date sifatida saqlaymiz:

js
import { configureStore, createSlice } from "@reduxjs/toolkit";
const orderSlice = createSlice({
  name: "buyurtma",
  initialState: { createdAt: null },
  reducers: {
    created(state, action) {
      state.createdAt = action.payload;
    },
  },
});
const store = configureStore({
  reducer: { buyurtma: orderSlice.reducer },
});
store.dispatch(orderSlice.actions.created(new Date(2026, 9, 14)));
console.log("davom etdi");

Node 24 (Toshkent vaqti), birinchi va oxirgi qatorlar:

text
A non-serializable value was detected in an action, in the path: `payload`. Value: 2026-10-13T19:00:00.000Z
…
A non-serializable value was detected in the state, in the path: `buyurtma.createdAt`. Value: 2026-10-13T19:00:00.000Z
…
davom etdi

Tarjimasi: "Amalda, payload yo'lida serializatsiya qilinmaydigan qiymat topildi", keyin xuddi shu — holatda, buyurtma.createdAt da. Bu console.error, istisno emas: kod davom etdi. Lekin jiddiy ogohlantirish: DevTools tarixi, persist va server bilan almashish JSON'ga tayanadi. Davosi — sanani satr yoki son qilib saqlash: created(new Date().toISOString()). Vaqtdagi 19:00 — UTC: Toshkentdagi 14-oktyabr 00:00 (Date darsidagi vaqt mintaqasi).

Ikkala tekshiruv faqat dev rejimida ishlaydi — production'da sekinlashtirmasin.

Tekshirib ko'ring: reducers: { cleared(state) { state = { items: [] }; } } — nega savat tozalanmaydi?

Javob

state — funksiya parametri, ya'ni mahalliy o'zgaruvchi. state = … faqat shu o'zgaruvchini boshqa obyektga yo'naltiradi; Immer bergan qoralama o'zgarmay qoladi va reducer undefined qaytaradi — Immer buni "o'zgarish yo'q" deb oladi. To'g'risi: state.items = [] (qoralamani o'zgartirish) yoki return { items: [] } (yangisini qaytarish).

4. React'da: Provider, useSelector, useDispatch

4.1 Ulash

Zustand'dan farqli, react-redux store'ni Context orqali beradi — ildizda bitta <Provider>:

jsx
import { configureStore, createSlice } from "@reduxjs/toolkit";
import { Provider, useDispatch, useSelector } from "react-redux";

const cartSlice = createSlice({
  name: "savat",
  initialState: { items: [], waiter: "Sardor" },
  reducers: {
    added(state, action) {
      state.items.push(action.payload);
    },
    waiterChanged(state, action) {
      state.waiter = action.payload;
    },
  },
});
const { added, waiterChanged } = cartSlice.actions;
const store = configureStore({
  reducer: { savat: cartSlice.reducer },
});

function CartBadge() {
  const count = useSelector((state) => state.savat.items.length);
  console.log("CartBadge chizildi:", count);
  return <p>Savatda: {count} ta</p>;
}

function Buttons() {
  const dispatch = useDispatch();
  return (
    <p>
      <button onClick={() => dispatch(added("Osh"))}>Osh</button>{" "}
      <button onClick={() => dispatch(waiterChanged("Malika"))}>
        Malika
      </button>
    </p>
  );
}

export default function App() {
  return (
    <Provider store={store}>
      <CartBadge />
      <Buttons />
    </Provider>
  );
}
  • useSelector(selektor) — holatdan qism o'qiydi. Zustand'dagidek: natija o'zgarmasa (===), komponent chizilmaydi.
  • useDispatch() — store.dispatch funksiyasini qaytaradi. U barqaror: komponent shu sababli chizilmaydi.
  • Provider — Context, lekin qiymat emas, store havolasini tarqatadi (Zustand II dagi Next.js naqshi kabi). Holat o'zgarganda Provider ostidagi hamma emas, faqat selektori boshqa natija bergan komponentlar chiziladi.

Buni pastdagi tuzoq misolida (Chrome'da) sanab ko'ramiz.

4.2 Tuzoq: selektor yangi obyekt qaytaradi

Zustand'dagi tuzoq Redux'da ham bor, faqat yumshoqroq. Yuqoridagi ilovaga ikkinchi komponent qo'shamiz — bitta selektor bilan ikki qiymat:

jsx
function Summary() {
  const { count, waiter } = useSelector((state) => ({
    count: state.savat.items.length,
    waiter: state.savat.waiter,
  }));
  console.log("Summary chizildi");
  return (
    <p>
      {count} ta, ofitsiant: {waiter}
    </p>
  );
}

Ilovani react-redux 9.3.0, React 19.3.0 bilan Chrome 155 da ishga tushirdik, «Osh» va «Malika» bosildi. Konsol:

text
CartBadge chizildi: 0
Selector unknown returned a different result when called with the same parameters. This can lead to unnecessary rerenders.
Selectors that return a new reference (such as an object or an array) should be memoized: https://redux.js.org/usage/deriving-data-selectors#optimizing-selectors-with-memoization
Summary chizildi
CartBadge chizildi: 1
Summary chizildi
Summary chizildi

Ikkinchi qator — react-redux'ning dev ogohlantirishi: "Nomsiz selektor bir xil kirishda boshqa natija qaytardi. Bu keraksiz qayta chizishga olib keladi." U selektorni ataylab ikki marta chaqirib, natijalarni solishtirgan. "unknown" — selektor funksiyasining nomi yo'q (o'q funksiya). Zustand 5 dagidek cheksiz sikl yo'q — react-redux bunday selektorni ko'tara oladi, lekin Summary har dispatch da chiziladi.

Pastki qatorlarga qarang. «Osh» bosilganda: CartBadge (son o'zgardi) va Summary. «Malika» bosilganda: faqat Summary — CartBadge ning soni o'zgarmadi, u chizilmadi. To'g'ri ishlayapti.

Davolari — Zustand'dagi bilan bir xil g'oya:

  • har qiymatga alohida useSelector;
  • ikkinchi argument — solishtirish funksiyasi: useSelector(fn, shallowEqual) (shallowEqual — react-redux'dan);
  • memoizatsiyalangan selektor — createSelector, keyingi darsda.

4.3 TypeScript: tiplangan hooklar

TypeScript bilan har useSelector((state: RootState) => …) da turni yozish — zerikarli va xatoga moyil. RTK 2 / react-redux 9 da bir marta tiplangan hook yasaladi — withTypes:

tsx
import { configureStore, createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
import { useDispatch, useSelector } from "react-redux";

type CartState = { items: string[] };
const initialState: CartState = { items: [] };

const cartSlice = createSlice({
  name: "savat",
  initialState,
  reducers: {
    added(state, action: PayloadAction<string>) {
      state.items.push(action.payload);
    },
  },
});

export const { added } = cartSlice.actions;
export const store = configureStore({
  reducer: { savat: cartSlice.reducer },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export const useAppSelector = useSelector.withTypes<RootState>();
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();

export function CartBadge() {
  const count = useAppSelector((state) => state.savat.itemz.length);
  const dispatch = useAppDispatch();
  return <button onClick={() => dispatch(added(5))}>{count}</button>;
}

Tip tekshiruvi (TypeScript 7.0.2, RTK 2.13.0 turlari bilan):

text
TS2551: Property 'itemz' does not exist on type 'CartState'. Did you mean 'items'?
TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

Ikkala xato ham ushlandi: noto'g'ri maydon nomi va noto'g'ri payload turi. Turlar qayerdan keldi?

  • PayloadAction<string> — amal turi: { type: string; payload: string }. Undan added yasovchisi (payload: string) ni oladi.
  • ReturnType<typeof store.getState> — holat turi store'dan chiqariladi (Utility types). Qo'lda yozilmaydi: yangi slice qo'shilsa, tur o'zi yangilanadi.
  • typeof store.dispatch — dispatch turi (keyingi darsdagi async amallarni ham taniydi).
  • withTypes<…>() — shu turlar "ichiga tikilgan" hook. Loyihada odatda hooks.ts faylida bir marta yoziladi va komponentlar faqat useAppSelector/useAppDispatch ni import qiladi.

Maslahat: eski kodda export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; ni ko'rasiz — bu react-redux 8 davridagi yozuv. Ma'nosi bir xil, yangi kodda withTypes ishlating.

Tekshirib ko'ring: Nega RootState turini qo'lda (type RootState = { savat: CartState }) yozish yomon odat?

Javob

Ikki haqiqat manbai paydo bo'ladi: configureStore dagi reducer obyekti va qo'lda yozilgan tur. Yangi slice (menyu) qo'shib, turni yangilashni unutsangiz, state.menyu TypeScript uchun "yo'q" bo'ladi yoki aksincha, eski maydon turda qolib ketadi. ReturnType<typeof store.getState> doim haqiqiy store'dan chiqariladi.

5. Kanon tajribasi: Redux Toolkit varianti (nega kanonga kirmadi)

Bu darsda vazifalar ga o'zgarish yo'q — holat Zustand'da qoladi (Zustand II, holat 22). Lekin biz qarorni taxmin bilan emas, o'lchov bilan qildik. Bir xil kichik vazifa — ro'yxatga qo'shish, belgilash, filtr va "qoldi" hisobi — yetti xil holat vositasi bilan yozildi va har biri build qilindi (tajriba/hajm.txt, gzip -9, React asosi bilan birga):

Bir xil vazifa — yetti holat vositasi (gzip, React bilan)
  • useReducer + Context67 980 B, 39 qator66,4 KB
  • Zustand68 367 B, 14 qator66,8 KB
  • Valtio69 710 B, 22 qator68,1 KB
  • Jotai70 925 B, 19 qator69,3 KB
  • Redux Toolkit76 233 B, 34 qator74,4 KB
  • MobX80 626 B, 28 qator78,7 KB
  • XState80 881 B, 42 qator79 KB

Manba: IlmHamroh kurs tajribasi (Vite 8.3.3, gzip -9, 2026-10-13)

Grafikda farq kichik ko'rinadi, chunki ustunlarning katta qismi React'ning o'zi (~66 KB). Kutubxona ulushini ajratib olsak (Context varianti — asos):

Variant Qo'shimcha gzip Holat kodi
Zustand 5.0.15 +0.4 KB 14 qator
Redux Toolkit 2.13.0 + react-redux 9.3.0 +8.3 KB 34 qator

Redux varianti — kanon tajribasidan (uzun qatorlar telefonga sig'ishi uchun bo'lingan):

tsx
// holat-redux.tsx — Redux Toolkit 2 + react-redux 9 (slice,
// store, hooklar)
import { configureStore, createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
import { Provider, useDispatch, useSelector } from "react-redux";
import { Korinish, ishgaTushir } from "./_umumiy.tsx";
import type { Filtr, Vazifa } from "./_umumiy.tsx";

const vazifalarSlice = createSlice({
  name: "vazifalar",
  initialState: {
    vazifalar: [] as Vazifa[],
    filtr: "hammasi" as Filtr,
  },
  reducers: {
    qosh(s, a: PayloadAction<string>) {
      s.vazifalar.push({
        id: s.vazifalar.length + 1,
        matn: a.payload,
        bajarildi: false,
      });
    },
    almashtir(s, a: PayloadAction<number>) {
      const v = s.vazifalar.find((x) => x.id === a.payload);
      if (v) v.bajarildi = !v.bajarildi;
    },
    filtrla(s, a: PayloadAction<Filtr>) {
      s.filtr = a.payload;
    },
  },
});
const store = configureStore({ reducer: vazifalarSlice.reducer });
type S = ReturnType<typeof store.getState>;
const { qosh, almashtir, filtrla } = vazifalarSlice.actions;

function Ilova() {
  const s = useSelector((x: S) => x);
  const d = useDispatch<typeof store.dispatch>();
  return (
    <Korinish
      holat={{
        ...s,
        qosh: (m) => d(qosh(m)),
        almashtir: (id) => d(almashtir(id)),
        filtrla: (f) => d(filtrla(f)),
      }}
    />
  );
}
ishgaTushir(
  <Provider store={store}>
    <Ilova />
  </Provider>,
);

Tajriba eslatmasi: bu yerda useSelector((x) => x) butun holatni oladi — o'lchov faqat hajm va kod qatori uchun, qayta chizish uchun emas. Bugungi darsni bilgan holda siz uni alohida selektorlarga bo'lgan bo'lardingiz.

Nega kanonga kirmadi (kanon qarori):

  • vazifalar ning client state'i kichik: e'lon va ikki sozlama. Server state — TanStack Query'da, URL — router'da, forma — React Hook Form'da. Store'ga qolgani uchun Redux'ning tartibi ortiqcha.
  • Zustand +0.4 KB va 14 qator; RTK +8.3 KB va 34 qator — xuddi shu ish uchun.
  • Marshrut action'idan e'lon qilish ikkalasida ham ishlaydi (store.dispatch ham hook'siz). Shuning uchun bu sabab hal qiluvchi emas edi.

Qachon Redux Toolkit — to'g'ri tanlov:

  • Loyiha allaqachon Redux'da (eng ko'p uchraydigan holat — yangi kodni RTK uslubida yozing).
  • Katta jamoa, ko'p modulli ilova: qat'iy "amal → reducer" tartibi, kod ko'rigida oldindan aytib bo'ladigan joylar.
  • DevTools'dagi tarix va time travel ish jarayonining bir qismi (murakkab xatolarni qayta o'ynatish).
  • Server state ham Redux'da bo'lishi kerak — RTK Query (RTK Query darsida).

6. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
TypeError: Cannot add property 2, object is not extensible holat reducer'dan tashqarida o'zgartirildi (Immer muzlatgan) o'zgarishni amal + reducer orqali
reducer chaqirildi, holat o'zgarmadi state = {…} — mahalliy o'zgaruvchi state.x = … yoki return {…}
A non-serializable value was detected in an action… Date, Map, funksiya — amal yoki holatda satr/son: toISOString(), oddiy obyekt
Selector unknown returned a different result… selektor har safar yangi obyekt/massiv alohida selektorlar, shallowEqual yoki createSelector
could not find react-redux context value; please ensure the component is wrapped in a <Provider> komponent Provider dan tashqarida (ko'pincha testda) ildizda (testda — wrapper'da) <Provider store>
TS: state — unknown oddiy useSelector, tur berilmagan useSelector.withTypes<RootState>()
reducer'da fetch yoki Math.random() reducer sof emas yon ta'sirlar — keyingi darsdagi thunk'larda

7. Mashqlar

Mashqlar kurs/mashqlar/19/23-redux-toolkit/ da. 1- va 2-mashq Redux Toolkit bilan Node'da ishlaydi: o'z papkangizda npm i --save-exact @reduxjs/toolkit@2.13.0, faylni .mjs qilib saqlang va node fayl.mjs. 3-mashq — saytda, «Ishga tushir» bilan.

1-mashq (oson): natijani ayting

Kod nima chiqaradi? Avval o'zingiz yozing, keyin ishga tushiring.

js
import { configureStore, createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
  name: "navbat",
  initialState: { number: 0 },
  reducers: {
    nextGuest(state) {
      state.number += 1;
    },
    skip(state, action) {
      state.number += action.payload;
    },
  },
});

const store = configureStore({ reducer: counterSlice.reducer });
const { nextGuest, skip } = counterSlice.actions;

store.dispatch(nextGuest());
store.dispatch(skip(5));
console.log(skip(5));
console.log(store.getState());
  • birinchi console.log →
  • ikkinchisi →
Yechim
text
{ type: 'navbat/skip', payload: 5 }
{ number: 6 }

skip(5) — faqat amal yasaydi, yubormaydi: dispatch siz holat o'zgarmaydi. Shuning uchun uchinchi chaqiruvdan keyin ham son 6 (1 + 5). Bu yerda reducer — bitta slice reduceri (obyekt emas), shuning uchun holat { navbat: … } emas, to'g'ridan-to'g'ri { number }.

2-mashq (o'rta): band stollar

«Bahor» zalidagi band stollar uchun slice yozing:

  1. name: "stollar", boshlang'ich holat { busy: [] }.
  2. occupied(id) — stol band bo'ladi (bir stol ikki marta qo'shilmasin).
  3. freed(id) — stol bo'shaydi.
  4. Store'da slice stollar kalitida; subscribe har o'zgarishda band: va ro'yxatni chiqarsin.
  5. Ketma-ketlik: 4 band, 7 band, yana 4 band, 4 bo'shadi.

Ishora: massivdan element olib tashlash — filter; bor-yo'qligi — includes.

Yechim
js
import { configureStore, createSlice } from "@reduxjs/toolkit";

const tablesSlice = createSlice({
  name: "stollar",
  initialState: { busy: [] },
  reducers: {
    occupied(state, action) {
      if (!state.busy.includes(action.payload)) {
        state.busy.push(action.payload);
      }
    },
    freed(state, action) {
      state.busy = state.busy.filter((id) => id !== action.payload);
    },
  },
});

const { occupied, freed } = tablesSlice.actions;
const store = configureStore({
  reducer: { stollar: tablesSlice.reducer },
});

store.subscribe(() =>
  console.log("band:", store.getState().stollar.busy),
);

store.dispatch(occupied(4));
store.dispatch(occupied(7));
store.dispatch(occupied(4));
store.dispatch(freed(4));

Node 24, RTK 2.13.0:

text
band: [ 4 ]
band: [ 4, 7 ]
band: [ 4, 7 ]
band: [ 7 ]

Uchinchi qatorga qarang: 4-stol allaqachon band edi, holat o'zgarmadi — lekin obunachi baribir chaqirildi. Redux'ning subscribe i "o'zgardimi" deb tekshirmaydi, har dispatch dan keyin xabar beradi. Solishtirishni useSelector qiladi — shuning uchun React komponenti bu holatda chizilmasdi. freed da ikkala yo'l ham ishlaydi: filter bilan yangi massivni qoralamaga yozish.

3-mashq (qiyin): o'quv modeliga combineReducers

«Jonli model» bo'limidagi createStore faqat bitta reducer oladi. Haqiqiy configureStore({ reducer: { savat, stollar } }) esa reducerlar obyektini qabul qiladi va har birini o'z kalitiga beradi. Shu ishni qiladigan combineReducers(reducers) yozing: u bitta reducer qaytaradi, u har kalit uchun reducers[kalit](state?.[kalit], action) ni chaqiradi.

Boshlang'ich kod saytda ishlaydi, lekin faqat savatni ko'rsatadi:

jsx
import { useSyncExternalStore } from "react";

function createStore(reducer) {
  let state = reducer(undefined, { type: "@@boshlash" });
  const listeners = new Set();
  return {
    getState: () => state,
    dispatch(action) {
      state = reducer(state, action);
      listeners.forEach((listener) => listener());
    },
    subscribe(listener) {
      listeners.add(listener);
      return () => listeners.delete(listener);
    },
  };
}

function cartReducer(state = [], action) {
  return action.type === "savat/added"
    ? [...state, action.payload]
    : state;
}

function tablesReducer(state = [], action) {
  return action.type === "stollar/occupied"
    ? [...state, action.payload]
    : state;
}

// combineReducers — shu yerga
const store = createStore(cartReducer);

function useSelector(selector) {
  return useSyncExternalStore(store.subscribe, () =>
    selector(store.getState()),
  );
}

export default function App() {
  const state = useSelector((s) => s);
  console.log(JSON.stringify(state));
  return (
    <>
      <button
        onClick={() =>
          store.dispatch({ type: "savat/added", payload: "Osh" })
        }
      >
        Osh
      </button>{" "}
      <button
        onClick={() =>
          store.dispatch({ type: "stollar/occupied", payload: 4 })
        }
      >
        4-stol
      </button>
    </>
  );
}

Konsolda:

text
[]
["Osh"]
Yechim
jsx
import { useSyncExternalStore } from "react";

function createStore(reducer) {
  let state = reducer(undefined, { type: "@@boshlash" });
  const listeners = new Set();
  return {
    getState: () => state,
    dispatch(action) {
      state = reducer(state, action);
      listeners.forEach((listener) => listener());
    },
    subscribe(listener) {
      listeners.add(listener);
      return () => listeners.delete(listener);
    },
  };
}

function cartReducer(state = [], action) {
  return action.type === "savat/added"
    ? [...state, action.payload]
    : state;
}

function tablesReducer(state = [], action) {
  return action.type === "stollar/occupied"
    ? [...state, action.payload]
    : state;
}

function combineReducers(reducers) {
  return (state, action) => {
    const next = {};
    let changed = state === undefined;
    for (const key of Object.keys(reducers)) {
      next[key] = reducers[key](state?.[key], action);
      if (state !== undefined && next[key] !== state[key]) {
        changed = true;
      }
    }
    return changed ? next : state;
  };
}

const store = createStore(
  combineReducers({ savat: cartReducer, stollar: tablesReducer }),
);

function useSelector(selector) {
  return useSyncExternalStore(store.subscribe, () =>
    selector(store.getState()),
  );
}

export default function App() {
  const state = useSelector((s) => s);
  console.log(JSON.stringify(state));
  return (
    <>
      <button
        onClick={() =>
          store.dispatch({ type: "savat/added", payload: "Osh" })
        }
      >
        Osh
      </button>{" "}
      <button
        onClick={() =>
          store.dispatch({ type: "stollar/occupied", payload: 4 })
        }
      >
        4-stol
      </button>
    </>
  );
}

Konsolda:

text
{"savat":[],"stollar":[]}
{"savat":["Osh"],"stollar":[]}
{"savat":["Osh"],"stollar":[4]}

Ikki nozik joy. Birinchisi: har amal hamma reducerlarga boradi — tablesReducer "savat/added" ni ham oladi va o'z holatini o'zgarmagan holda qaytaradi. Ikkinchisi: hech bir bo'lak o'zgarmasa, combineReducers eski obyektni qaytaradi (changed). Aks holda har noma'lum amal yangi ildiz obyekti yasab, hamma selektorlarni "o'zgardi" deb aldardi. Haqiqiy Redux'ning combineReducers i ham aynan shunday qiladi.

4-mashq (tahlil): kanon tajribasini o'qing

«Kanon tajribasi» bo'limidagi holat-redux.tsx va o'tgan darslardagi holat-zustand.tsx ni solishtiring. Savollar:

  1. Redux variantida Korinish ga berilgan qosh — har renderda yangi funksiya. Zustand variantida-chi?
  2. Sizning jamoangiz 30 kishi, ilova 5 yoshda va Redux'da. Yangi "sevimli taomlar" holati uchun Zustand qo'shasizmi?
Yechim
  1. Zustand variantida qosh store yaratilganda bir marta yasalgan — barqaror havola. Redux variantida (m) => d(qosh(m)) har chizishda yangi o'q funksiya. Korinish memo bilan o'ralganda bu farq qilardi (memo, useMemo, useCallback). Redux'da odatda dispatch ni to'g'ridan-to'g'ri komponentda chaqirasiz, shuning uchun bunday o'rovchi kerak bo'lmaydi.
  2. Odatda — yo'q. Ikkinchi holat kutubxonasi jamoaga ikki xil qoida, ikki DevTools va ikki xil test usulini beradi. Yangi bo'lakni RTK slice qilib yozish izchilroq. Zustand qo'shish asosli bo'ladigan holat — Redux'dan butunlay ko'chish rejasi bo'lsa va bu bosqichma-bosqich qilinsa.

8. Real ishda

  • Qayerda: bank, telekom, e-commerce — ko'p yillik katta React ilovalarining katta qismi Redux'da. Yangi kod u yerda RTK bilan yoziladi; eski switch/connect() kodini o'qish ham kerak bo'ladi.
  • connect() va klass komponentlar: eski kodda connect(mapStateToProps)(Component) ni ko'rasiz — bu HOC naqshi, hooklardan oldingi usul. Ma'nosi useSelector + useDispatch bilan bir xil.
  • Redux DevTools — amallar tarixi, holat farqi, time travel. RTK'da configureStore uni o'zi ulaydi (dev rejimida).
  • Intervyu savollari: "Flux oqimini tushuntiring", "reducer nega sof bo'lishi kerak?", "createSlice ichida state.push qilsa bo'ladimi — nega?", "Redux va Zustand — qachon qaysi?", "useSelector qachon qayta chizadi?".

Xulosa

  • Redux — yagona store, o'zgarish faqat amal (dispatch) orqali, yangi holatni sof reducer hisoblaydi: bir tomonlama Flux oqimi.
  • createSlice — reducer va amal yasovchilar birga; amal turi slice/reducer; ichida Immer — qoralamani o'zgartirish yoki yangi qiymat qaytarish.
  • configureStore — reducerlar obyekti, DevTools va dev tekshiruvlari: muzlatilgan holat, serializatsiya ogohlantirishi.
  • React'da — <Provider store>, useSelector, useDispatch; yangi obyekt qaytaradigan selektor ortiqcha chizadi.
  • TypeScript'da — RootState/AppDispatch store'dan chiqariladi, useSelector.withTypes bilan tiplangan hooklar.
  • Tajriba: bir xil vazifa uchun RTK +8.3 KB va 34 qator, Zustand +0.4 KB va 14 qator — vazifalar da Zustand qoldi; Redux — katta, mavjud Redux loyihalarda to'g'ri tanlov.

Keyingi dars: Redux Toolkit II: async, selektorlar va normalizatsiya — createAsyncThunk bilan serverga so'rov, createSelector, createEntityAdapter va listener middleware.

Manbalar

  • Redux Toolkit: Quick Start, createSlice, configureStore — redux-toolkit.js.org (v2.13, 2026-10 holatiga)
  • Redux: Redux Essentials, Style Guide (amal nomlari — voqea sifatida) — redux.js.org/style-guide
  • React Redux: Hooks (useSelector, useDispatch, withTypes), dev-mode checks — react-redux.js.org/api/hooks
  • Redux: Getting Started with TypeScript — redux.js.org/usage/usage-with-typescript
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Redux Toolkit: configureStore, createSlice va useSelector bilan global holat — IlmHamroh