12.2-bob: Redux Toolkit (store, slice, async thunk)
12-QISM — State Management va Data Fetching · 2-mavzu
1. Kirish va motivatsiya
12.1-bobda Context'ni chuqur o'rgandik va uning cheklovlarini ko'rdik: selektor yo'q (qism tinglab bo'lmaydi), katta/tez o'zgaradigan state'da ko'p re-render, va DevTools/debugging imkoniyatlari cheklangan. Aynan shu cheklovlar paydo bo'lganda — ya'ni katta, murakkab ilovada (ko'p bog'liq state, murakkab yangilanishlar, kuchli debugging ehtiyoji, jamoaviy ish) — Redux keladi. Redux — frontend state management'ning eng mashhur, eng ko'p ishlatiladigan va eng "bashoratli" yechimi: butun ilova holatini bitta markaziy joyda (store), qat'iy qoidalar bilan boshqaradi.
Lekin eski Redux'ning yomon nomi bor edi: u juda ko'p boilerplate (takroriy, zerikarli kod) talab qilardi — har action uchun konstanta, action creator, reducer, switch... oddiy "hisoblagich"ni yozish uchun 5 fayl kerak bo'lardi. Aynan shu sababli ko'p dasturchi Redux'dan qochardi. Redux Toolkit (RTK) — Redux jamoasining o'zi yaratgan rasmiy, zamonaviy yechim: u boilerplate'ni keskin kamaytiradi (createSlice — reducer va action'ni avtomatik), Immerni o'z ichiga oladi (state'ni "mutatsiya qilgandek" yozasiz, lekin natija immutable), va configureStore (DevTools, middleware avtomatik sozlab keladi). Bugun "Redux yoz" degani "Redux Toolkit yoz" degani (eski qo'lda Redux endi yozilmaydi — rasman tavsiya etilmaydi).
Bu bob: nega global state, nega Redux (Context cheklovi — 12.1), Redux asosiy tushunchalari (store, action, reducer, dispatch — Flux/bir tomonlama oqim), nega RTK (boilerplate muammosi), configureStore (store sozlash), createSlice (reducer + action avtomatik), Immer (mutatsiya sintaksisi — immutable natija), useSelector/useDispatch (tabiiy selektor — Context cheklovini hal qiladi), createAsyncThunk (async — loading/error holatlari), Redux DevTools (vaqt sayohati — debugging), createEntityAdapter (normalizatsiya), memoized selektorlar (createSelector), TypeScript bilan RTK (typed hooks), va qachon Redux (Context/Zustand bilan taqqos) — mavzuni to'liq, zamonaviy (RTK) va amaliy holatda ochamiz.
O'xshatish: Redux — bu davlatning markaziy bank va qonun tizimi. Context 12.1-bob — bu mahallada pul almashish: oddiy, tez, lekin katta miqyosda tartibsiz bo'ladi (kim qancha, qachon o'zgartirdi — kuzatib bo'lmaydi). Redux — bu markaziy bank (store — barcha pul/holat bitta joyda) + qonun (reducer — pulni faqat qonuniy yo'l bilan, action orqali o'zgartirish mumkin) + rasmiy yozuvlar (har tranzaksiya — action — qaydlanadi, DevTools'da ko'rinadi). Hech kim pulni to'g'ridan o'zgartira olmaydi — faqat rasmiy so'rov (action) yuboradi, bank (reducer) qoidaga qarab bajaradi. Bu — biroz ko'proq rasmiyatchilik, lekin bashoratli, kuzatiladigan, ishonchli (katta tizim uchun zarur). Redux Toolkit — bu shu bankning zamonaviy, raqamli versiyasi: ilgari har tranzaksiya uchun 5 ta qog'oz to'ldirilardi (boilerplate), endi bir tugma (
createSlice).
Nega muhim?
- Katta ilova standarti — murakkab, ko'p holatli ilovada Redux — eng keng yechim (jamoaviy, bashoratli).
- Bashoratlilik + debugging — DevTools (vaqt sayohati), qat'iy oqim — xato topishni osonlashtiradi.
- Stack'da bor — Redux Toolkit va RTK Query zamonaviy React stack'ining muhim qismi (ko'p loyihada uchraydi).
- Ish bozori — ko'p kompaniya Redux ishlatadi (legacy va yangi) — bilish majburiy.
2. Nazariya — chuqur tushuntirish
2.1. Nega global state, nega Redux
CONTEXT YETMAYDI QACHON (12.1: 2.9, 2.10):
Katta, murakkab state (ko'p bog'liq qism)
Tez-tez o'zgaradigan (selektor yo'q ko'p re-render — 12.1: 2.3)
Murakkab yangilanishlar (ko'p joydan bir state'ni o'zgartirish)
Kuchli debugging (vaqt sayohati, har o'zgarishni kuzatish)
Jamoaviy (qat'iy struktura — har kim bir xil naqshda)
REDUX BERADIGAN NARSALAR:
Markaziy store (butun holat bitta joyda — "single source of truth")
Bashoratli oqim (faqat action orqali o'zgartirish — qat'iy)
Tabiiy selektor (useSelector — faqat kerakli qism tinglash — 2.7)
DevTools (vaqt sayohati, har action ko'rinadi — 2.9)
Middleware (async, logging, custom mantiq — 2.8)
Context — kichik/oddiy global state; Redux — KATTA, MURAKKAB, kuzatiladigan state
Redux — "boilerplate ko'p" edi Redux Toolkit buni HAL QILDI (2.4)Nega global state, nega Redux — Redux'ning o'rnini tushunish. Context qachon yetmaydi (12.1: 2.9, 2.10): katta, murakkab state (ko'p bog'liq qism); tez-tez o'zgaradigan (selektor yo'q — ko'p re-render — 12.1: 2.3); murakkab yangilanishlar (ko'p joydan bir state'ni o'zgartirish); kuchli debugging ehtiyoji (vaqt sayohati); jamoaviy ish (qat'iy struktura). Redux beradigan narsalar: (1) markaziy store (butun holat bitta joyda — "single source of truth"); (2) bashoratli oqim (state'ni faqat action orqali o'zgartirish — qat'iy, kuzatiladigan); (3) tabiiy selektor (
useSelector— faqat kerakli qism tinglash — Context'ning asosiy cheklovini hal qiladi — 2.7); (4) DevTools (vaqt sayohati — har action va state o'zgarishini ko'rish, hatto "orqaga qaytarish" — 2.9); (5) middleware (async, logging, custom mantiq — 2.8). Ikki nuqta: (1) Context — kichik/oddiy global state uchun (theme, auth), Redux — katta, murakkab, kuzatiladigan state uchun; (2) eski Redux'ning asosiy kamchiligi "boilerplate ko'p" edi — Redux Toolkit 2.4-bob buni hal qildi (shuning uchun bugun Redux = RTK). Bu — Redux'ni qachon ishlatish qarorining asosi.
2.2. Redux asosiy tushunchalari — Flux va bir tomonlama oqim
REDUX UCH TUSHUNCHA (Flux arxitekturasi — bir tomonlama oqim):
┌─────────────────────────────────────────────────────────────┐
│ 1. STORE — butun holat (state) BITTA obyektda (single source)│
│ { user: {...}, cart: [...], theme: "dark" } │
│ │
│ 2. ACTION — "nima bo'ldi" (obyekt — type + payload): │
│ { type: "cart/add", payload: product } │
│ │
│ 3. REDUCER — eski state + action yangi state (sof funksiya):│
│ (state, action) => newState │
└─────────────────────────────────────────────────────────────┘
BIR TOMONLAMA OQIM (predictable — bashoratli):
Komponent ──dispatch(action)──► Store (reducer ishlaydi) ──► yangi state ──► komponent render
▲ │
└────────────────────── useSelector (o'qiydi) ◄─────────────────────────────┘
State'ni FAQAT action orqali o'zgartirish (to'g'ridan emas — qat'iy oqim)
Reducer SOF (11.6: 2.3) — bir xil action bir xil natija (bashoratli, sinaladigan)Redux asosiy tushunchalari — uch ustun (Flux arxitekturasi — bir tomonlama ma'lumot oqimi). (1) Store — butun ilova holati (state) bitta obyektda (
{ user, cart, theme }— "single source of truth"). (2) Action — "nima bo'ldi"ni tasvirlovchi obyekt ({ type: "cart/add", payload: product }—typevapayload— 11.6: 2.4 dagiuseReduceraction bilan bir xil g'oya). (3) Reducer — eski state va action olib, yangi state qaytaruvchi sof funksiya ((state, action) => newState— 11.6: 2.3). Bir tomonlama oqim (bashoratlilikning kaliti): komponentdispatch(action)chaqiradi store'da reducer ishlaydi yangi state komponent re-render (useSelectororqali o'qiydi). Ma'lumot doim bir yo'nalishda aylanadi (komponent action store komponent). Ikki tamoyil: (1) state'ni faqat action orqali o'zgartirish mumkin (to'g'ridan emas — bu qat'iylik bashoratlilik beradi: har o'zgarish kuzatiladi); (2) reducer sof bo'lishi shart (11.6: 2.3 — side effect yo'q, immutable) — shuning uchun bir xil action har doim bir xil natija beradi (bashoratli, sinaladigan, DevTools'da qaytariladigan). Bu uch tushuncha — Redux'ning falsafasi (RTK ularni soddalashtiradi, lekin asos bir xil).
2.3. Redux store qatlamlari (umumiy ko'rinish)
REDUX QISMLARI (RTK bilan — qanday joylashadi):
store.ts ──────────── configureStore({ reducer: { cart, auth, ... } })
│ (barcha slice'larni birlashtiradi + DevTools/middleware)
▼
features/cart/cartSlice.ts ── createSlice({ name, initialState, reducers })
│ (cart holati + uni o'zgartiruvchi reducer'lar)
▼
Komponent ──── useSelector(s => s.cart) + useDispatch()
(o'qish) (yozish — dispatch(action))
PROVIDER (Context kabi — store'ni ilovaga beradi):
<Provider store={store}> // react-redux Provider (ildizda)
<App />
</Provider>
FAYL TUZILISHI (feature-based — 11.3: 2.9):
src/
├── app/store.ts configureStore
└── features/
├── cart/cartSlice.ts cart slice
└── auth/authSlice.ts auth slice
store (markaz) + slice'lar (har feature holati) + Provider (ilovaga ulashadi)
Feature-based — har slice o'z papkasida (cart/, auth/ — 11.3: 2.9)Redux store qatlamlari — RTK bilan Redux qanday joylashishini ko'rsatadi. Asosiy qismlar: (1)
store.ts—configureStore({ reducer: { cart, auth } })(barcha slice'larni birlashtiradi va DevTools/middleware'ni avtomatik sozlaydi — 2.4); (2) slice fayllari (features/cart/cartSlice.ts) —createSlicebilan har feature'ning holati va uni o'zgartiruvchi reducer'lari; (3) komponent —useSelector(s => s.cart)(o'qish) vauseDispatch()(yozish — 2.7). Provider (react-redux) — store'ni butun ilovaga beradi:<Provider store={store}><App /></Provider>(ildizda, Context Provider kabi). Fayl tuzilishi — feature-based (11.3: 2.9):app/store.ts(markaz),features/cart/cartSlice.ts,features/auth/authSlice.ts(har feature o'z papkasida). Ikki nuqta: (1) Redux uch qismdan iborat — store (markaz), slice'lar (har feature holati), Provider (ilovaga ulashadi); (2) feature-based tuzilish — har slice o'z papkasida (cart, auth — kengaytiriladigan, tartibli). Bu — Redux ilovasining umumiy arxitekturasi (har qismni keyingi bo'limlarda batafsil ko'ramiz).
2.4. configureStore va createSlice — RTK asoslari
createSlice — reducer va action'ni AVTOMATIK (eski Redux boilerplate'ini yo'qotadi):
import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter", // slice nomi (action type prefiksi: "counter/...")
initialState: { value: 0 }, // boshlang'ich holat
reducers: { // o'zgartiruvchi funksiyalar:
increment: (state) => { state.value += 1; }, // "mutatsiya" (Immer — 2.5)
decrement: (state) => { state.value -= 1; },
addBy: (state, action) => { state.value += action.payload; }, // payload bilan
},
});
export const { increment, decrement, addBy } = counterSlice.actions; // action'lar AVTOMATIK
export default counterSlice.reducer; // reducer
configureStore — store yaratadi (DevTools/middleware AVTOMATIK):
const store = configureStore({
reducer: { counter: counterSlice.reducer }, // slice'larni birlashtir
});
createSlice — name + initialState + reducers action va reducer AVTOMATIK (boilerplate yo'q)
configureStore — DevTools, thunk middleware, immutability tekshiruvi AVTOMATIK
configureStorevacreateSlice— RTK'ning ikki asosiy vositasi (eski Redux boilerplate'ini yo'qotadi).createSlice— bir joyda slice nomi, boshlang'ich holat, va reducer'larni belgilaydi, va undan action'larni avtomatik generatsiya qiladi:createSlice({ name: "counter", initialState: { value: 0 }, reducers: { increment: (state) => { state.value += 1 } } })—name(action type prefiksi —"counter/increment"),initialState, vareducers(o'zgartiruvchi funksiyalar). Diqqat: reducer ichidastate.value += 1— "mutatsiya"dek ko'rinadi, lekin bu Immer tufayli xavfsiz 2.5-bob.createSlicedan action creator'lar (increment,decrement) va reducer avtomatik chiqadi (counterSlice.actions,counterSlice.reducer) — eski Redux'da bularning har birini qo'lda yozish kerak edi.configureStore— store yaratadi va DevTools, thunk middleware (async uchun), immutability/serializability tekshiruvini avtomatik sozlaydi (eski Redux'da bularning hammasini qo'lda ulash kerak edi). Ikki nuqta: (1)createSlice— name + initialState + reducers'dan action va reducer'ni avtomatik chiqarads (boilerplate'ni yo'qotadi — RTK'ning eng katta yutug'i); (2)configureStore— best-practice middleware va DevTools'ni avtomatik beradi. Bu ikkisi — zamonaviy Redux'ning asosi (eski qo'lda Redux endi yozilmaydi).
2.5. Immer — mutatsiya sintaksisi, immutable natija
IMMER — RTK ICHIDA (state'ni "mutatsiya qilgandek" yoziladi, natija IMMUTABLE):
// Eski Redux (qo'lda immutable — zerikarli — 11.4: 2.7):
case "add": return { ...state, items: [...state.items, action.payload] };
case "update": return { ...state, user: { ...state.user, name: action.payload } };
// RTK + Immer (mutatsiya sintaksisi — Immer immutable qiladi):
reducers: {
add: (state, action) => { state.items.push(action.payload); }, // push — Immer OK
update: (state, action) => { state.user.name = action.payload; }, // to'g'ridan — OK
}
QANDAY ISHLAYDI (ichki):
- Immer "draft" (qoralama) yaratadi — unga mutatsiya qilinadi
- Immer o'zgarishlarni kuzatib, YANGI immutable obyekt hosil qiladi (eski tegmaydi)
"mutatsiya" yoziladi, lekin natija immutable (ikkalasining foydasi)
FAQAT createSlice/createReducer ICHIDA mutatsiya mumkin (Immer o'rab turibdi)
Tashqarida (oddiy funksiya) mutatsiya — XATO (Immer yo'q — immutability buziladi — 11.4: 2.7)Immer — RTK'ning ichidagi kutubxona, u state'ni "mutatsiya qilgandek" yozishga imkon beradi, lekin natija immutable bo'ladi. Eski Redux'da immutability'ni qo'lda saqlash kerak edi (
{ ...state, items: [...state.items, payload] }— zerikarli, xatoga moyil, ayniqsa ichma-ich obyektlarda — 11.4: 2.7, 2.8). RTK + Immer bilan esa to'g'ridan mutatsiya sintaksisi ishlatasiz:state.items.push(payload),state.user.name = payload(push, to'g'ridan tayinlash — odatda taqiqlangan, lekin bu yerda xavfsiz). Qanday ishlaydi (ichki mexanizm): Immer "draft" (qoralama nusxa) yaratadi, siz unga mutatsiya qilasiz, Immer o'zgarishlarni kuzatib yangi immutable obyekt hosil qiladi (eski state tegmaydi) — natijada siz mutatsiyaning qulayligini (oddiy yozuv) va immutability'ning foydasini (bashoratlilik, performance) ikkalasini olasiz. Eng muhim ogohlantirish: mutatsiya sintaksisi faqatcreateSlice/createReducerichida ishlaydi (Immer o'rab turibdi); tashqarida (oddiy funksiya, useState'da) mutatsiya — xato (Immer yo'q, immutability buziladi — 11.4: 2.7). Bu farqni anglash muhim — RTK ichida mutatsiya OK, tashqarida taqiq. Immer — RTK'ni yozishda eng yoqimli qilgan xususiyat.
2.6. useSelector va useDispatch — komponent bilan ulanish
react-redux hooklari (store'ni komponentga ulaydi):
// useSelector — store'dan KERAKLI qismni O'QISH (tabiiy selektor — 12.1: 2.9 yechimi):
const count = useSelector((state) => state.counter.value); // faqat counter.value
const items = useSelector((state) => state.cart.items);
// useDispatch — action YUBORISH (yozish):
const dispatch = useDispatch();
dispatch(increment()); // action creator (createSlice'dan — 2.4)
dispatch(addBy(5)); // payload bilan
SELEKTOR KUCHI (Context'dan farq — 12.1: 2.3):
useSelector(s => s.counter.value) — FAQAT counter.value o'zgarganda komponent render
cart yoki auth o'zgarsa — BU komponent render BO'LMAYDI (faqat tinglagan qism)
┌────────────────────────────────────────────────────────────┐
│ useSelector: store'dan qism o'qish (faqat o'sha qism o'zgarsa │
│ render) | useDispatch: action yuborish (o'zgartirish) │
└────────────────────────────────────────────────────────────┘
useSelector — TABIIY selektor (faqat tinglagan qism o'zgarsa render — Context'ning yechimi)
useSelector qiymat REFERENTIAL teng bo'lsa render yo'q (obyekt qaytarsa — memoize — 2.10)
useSelectorvauseDispatch— react-redux hooklari, store'ni komponentga ulaydi.useSelector(selektor)— store'dan kerakli qismni o'qiydi:const count = useSelector((state) => state.counter.value)— bu tabiiy selektor (12.1: 2.9 dagi Context cheklovining yechimi): komponent faqat o'zi tinglagan qism (counter.value) o'zgarganda render bo'ladi;cartyokiautho'zgarsa — bu komponent render bo'lmaydi. Bu — Redux'ning Context'dan eng katta afzalligi: fine-grained reactivity (faqat kerakli qismga reaksiya).useDispatch()— action yuboruvchidispatchfunksiyasini qaytaradi:dispatch(increment()),dispatch(addBy(5))(action creator'lar —createSlice'dan, payload bilan). Ikki nuqta: (1)useSelector— tabiiy selektor (faqat tinglagan qism o'zgarganda render — Context'ning asosiy muammosini hal qiladi); (2)useSelectorqaytargan qiymat referential teng bo'lsa (===) render bo'lmaydi — lekin agar selektor yangi obyekt qaytarsa (s => ({ a: s.a })) har safar yangi havola har render re-render (bu yerda memoized selektor kerak —createSelector— 2.10). Bu hooklar — Redux'ni komponent bilan bog'lashning standart usuli.
2.7. createAsyncThunk — async (loading/error)
createAsyncThunk — ASYNC amal (API so'rovi) + loading/error AVTOMATIK:
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
// 1. Async thunk (API so'rovi):
export const fetchUsers = createAsyncThunk("users/fetch", async () => {
const res = await fetch("/api/users");
return res.json(); // bu — fulfilled payload bo'ladi
});
// 2. Slice — thunk'ning 3 holatini boshqaradi (extraReducers):
const usersSlice = createSlice({
name: "users",
initialState: { items: [], loading: false, error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => { state.loading = true; }) // boshlandi
.addCase(fetchUsers.fulfilled, (state, action) => { // muvaffaqiyat
state.loading = false; state.items = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => { // xato
state.loading = false; state.error = action.error.message;
});
},
});
// Komponentda: dispatch(fetchUsers()); pending fulfilled/rejected (avtomatik)
createAsyncThunk — async + 3 holat (pending/fulfilled/rejected) AVTOMATIK
extraReducers — thunk holatlarini boshqaradi (loading/error qo'lda — lekin tartibli)
createAsyncThunk— async amallar (API so'rovi) va ularning loading/error holatlarini boshqaradi. Eski Redux'da async — murakkab edi (qo'ldadispatchketma-ket, loading flag'lar). RTK soddalashtiradi: (1) thunk yaratish —createAsyncThunk("users/fetch", async () => { const res = await fetch(...); return res.json() })— birinchi argument action type prefiksi, ikkinchi async funksiya (qaytargan qiymat —fulfilledpayload). (2) slice'da 3 holatni boshqarish —extraReducers(builder) bilan:pending(so'rov boshlandi —loading = true),fulfilled(muvaffaqiyat —items = payload,loading = false),rejected(xato —error = action.error.message). Komponentdadispatch(fetchUsers())— RTK avtomatikpendingfulfilled/rejectedaction'larini yuboradi. Ikki nuqta: (1)createAsyncThunk— async + uch holat (pending/fulfilled/rejected) avtomatik (qo'lda loading flag boshqarish kerak emas — RTK qiladi); (2)extraReducers— thunk holatlarini slice'da boshqarish (loading/error qo'lda yoziladi, lekin tartibli va bashoratli). MUHIM: server ma'lumot uchuncreateAsyncThunkishlab bo'ladi, lekin ko'p holatda RTK Query 12.3-bob yoki TanStack Query 12.4-bob ancha yaxshiroq (kesh, fonda yangilash, qayta urinish avtomatik — 12.1: 2.10).createAsyncThunk— oddiy async yoki client-side async amallar uchun.
2.8. Middleware va Redux DevTools
MIDDLEWARE — action'ning store'ga yetishidan OLDIN oraliq mantiq (logging, async, custom):
dispatch(action) ──► [middleware 1] ──► [middleware 2] ──► reducer ──► store
(har action shu yerdan o'tadi — logging, async, analytics)
RTK'da avtomatik middleware (configureStore):
- redux-thunk (async — createAsyncThunk ishlashi uchun)
- immutability tekshiruvi (dev — mutatsiyani tutadi)
- serializability tekshiruvi (dev — non-serializable state'ni tutadi)
CUSTOM middleware (masalan logging):
const logger = (store) => (next) => (action) => {
console.log("dispatch:", action.type);
return next(action); // keyingi middleware/reducer'ga uzat
};
REDUX DEVTOOLS (brauzer kengaytmasi — Redux'ning super-kuchi):
Har action va state o'zgarishini ko'rish (kuzatish)
VAQT SAYOHATI — oldingi holatga qaytarish (debug)
Action'ni "qayta o'ynatish", state'ni tekshirish
Middleware — action oqimiga oraliq mantiq (RTK avtomatik thunk/tekshiruv beradi)
DevTools — Redux'ning eng katta afzalligi (vaqt sayohati — debugging — 2.1)Middleware va Redux DevTools — Redux'ning ikki kuchli imkoniyati. Middleware — action store'ga (reducer'ga) yetishidan oldin ishlaydigan oraliq mantiq: har dispatch qilingan action middleware zanjiri orqali o'tadi (logging, async, analytics, custom). RTK'da
configureStoreavtomatik muhim middleware'ni qo'shadi: redux-thunk (async —createAsyncThunkishlashi uchun), immutability tekshiruvi (development — agar state'ni tashqarida mutatsiya qilsangiz tutadi), serializability tekshiruvi (non-serializable qiymat — masalan Promise, funksiya — state'da bo'lsa ogohlantiradi). Custom middleware ham yozish mumkin ((store) => (next) => (action) => {...}— masalan har action'ni loglash). Redux DevTools (brauzer kengaytmasi) — Redux'ning super-kuchi: har action va state o'zgarishini ko'rish (kuzatish — qaysi action, qanday o'zgartirdi), vaqt sayohati (oldingi holatlarga qaytarish — xatoni topish uchun bebaho), action'ni qayta o'ynatish, state'ni tekshirish. Ikki nuqta: (1) middleware — action oqimiga oraliq mantiq (RTK avtomatik thunk va dev-tekshiruvlarni beradi); (2) DevTools — Redux'ning Context/Zustand'dan eng katta afzalligi (vaqt sayohati bilan murakkab state buglarini topish — 2.1). Bu — Redux'ni "katta, murakkab ilova"da ishlatishning asosiy sabablaridan biri.
2.9. createEntityAdapter — normalizatsiya
NORMALIZATSIYA — ro'yxatni ID bo'yicha OBYEKT sifatida saqlash (tez qidiruv/yangilash):
Massiv (qidirish/yangilash — O(n) — sekin katta ro'yxatda):
items: [{ id: "1", name: "A" }, { id: "2", name: "B" }]
// yangilash: items.map(i => i.id === "1" ? {...i, ...} : i) — butun massivni aylanish
Normalizatsiya (ID obyekt — O(1) qidiruv/yangilash):
{ ids: ["1", "2"], entities: { "1": {name:"A"}, "2": {name:"B"} } }
// yangilash: entities["1"] = {...} — to'g'ridan (tez)
createEntityAdapter — normalizatsiyani AVTOMATIK qiladi:
const usersAdapter = createEntityAdapter();
const usersSlice = createSlice({
name: "users",
initialState: usersAdapter.getInitialState({ loading: false }),
reducers: {
addUser: usersAdapter.addOne, // tayyor CRUD reducer'lar
updateUser: usersAdapter.updateOne,
removeUser: usersAdapter.removeOne,
},
});
// Selektorlar ham avtomatik: usersAdapter.getSelectors()
createEntityAdapter — ro'yxatni normalize (IDobyekt) + CRUD reducer/selektor AVTOMATIK
Katta ro'yxat (foydalanuvchi, mahsulot)da tez qidiruv/yangilash (O(1) — performance)
createEntityAdapter— ro'yxat (kolleksiya) holatini boshqarishning ilg'or, performant usuli. Normalizatsiya — ro'yxatni massiv o'rniga ID bo'yicha obyekt sifatida saqlash. Massivda element qidirish/yangilashO(n)(butun massivni aylanish —items.map(...)— sekin katta ro'yxatda); normalize qilingan shaklda ({ ids: [...], entities: { "1": {...} } }) — qidirish/yangilashO(1)(entities["1"]— to'g'ridan, tez).createEntityAdapter— bu normalizatsiyani avtomatik qiladi:usersAdapter.getInitialState()(normalize qilingan boshlang'ich holat), va tayyor CRUD reducer'lar (addOne,updateOne,removeOne,setAll...) hamda selektorlar (getSelectors—selectAll,selectById). Slice'da:reducers: { addUser: usersAdapter.addOne, updateUser: usersAdapter.updateOne }. Ikki nuqta: (1)createEntityAdapter— ro'yxatni normalize qiladi va CRUD reducer/selektorni avtomatik beradi (qo'lda yozish kerak emas); (2) katta ro'yxat (foydalanuvchilar, mahsulotlar, buyurtmalar)da tez qidiruv/yangilash (O(1)— performance). Bu — Redux'da ro'yxat-holat boshqaruvining professional naqshi (lekin server ro'yxati uchun ko'pincha RTK/TanStack Query afzal — 12.3, 12.4).
2.10. Memoized selektorlar — createSelector
MUAMMO: useSelector YANGI obyekt/hisob qaytarsa — har render re-render (referential — 2.6):
const filtered = useSelector(s => s.items.filter(i => i.active)); // HAR render yangi massiv!
// filter har safar yangi massiv useSelector "o'zgardi" deb biladi har render re-render
createSelector — natijani KESHLAYDI (kirish o'zgarmasa — eski natija):
import { createSelector } from "@reduxjs/toolkit";
const selectActiveItems = createSelector(
(state) => state.items, // kirish 1 (input selektor)
(items) => items.filter((i) => i.active) // hisob (kirish o'zgargandagina qayta hisob)
);
const filtered = useSelector(selectActiveItems); // memoized — kerakli render
// Argument bilan (parametrli selektor):
const selectById = createSelector(
[(state) => state.items, (state, id) => id],
(items, id) => items.find((i) => i.id === id)
);
createSelector — qimmat/hosilangan hisobni keshlaydi (reselect — 11.6: 2.8 g'oyasi)
useSelector'da YANGI obyekt qaytaradigan selektor createSelector (aks holda har render re-render)Memoized selektorlar —
createSelector—useSelectorning performance tuzog'ini hal qiladi. Muammo: agaruseSelectoryangi obyekt yoki hisoblangan qiymat qaytarsa (s => s.items.filter(i => i.active)—filterhar safar yangi massiv yaratadi),useSelectoruni===bilan solishtirganda "o'zgardi" deb biladi (referential — 2.6) komponent har render re-render bo'ladi (garchi ma'lumot o'zgarmagan bo'lsa ham). Yechim —createSelector(RTK'ningreselect'i) — natijani keshlaydi:createSelector((state) => state.items, (items) => items.filter(i => i.active))— birinchi (input) selektor kirishni oladi, ikkinchi funksiya hisoblaydi (faqat kirish o'zgarganda qayta hisoblab, aks holda oldingi natijani qaytaradi).const filtered = useSelector(selectActiveItems)— endi memoized (faqatitemso'zgarganda yangi). Parametrli selektor ham mumkin (id bilan qidirish). Ikki nuqta: (1)createSelector— qimmat yoki hosilangan (filter/sort/hisob) qiymatni keshlaydi (useMemoning Redux selektor versiyasi — 11.6: 2.8 g'oyasi); (2)useSelectorda yangi obyekt/massiv qaytaradigan selektor uchuncreateSelectormajburiy (aks holda har render re-render — eng keng Redux performance tuzog'i). Bu — Redux'ni performant ishlatishning zarur naqshi.
2.11. TypeScript bilan RTK va qachon Redux
TYPED HOOKS (RTK + TypeScript — 11.14):
// store turlarini chiqarish:
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// Typed hooklar (har joyda qayta yozmaslik):
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// Komponentda — to'liq type-safe:
const count = useAppSelector((state) => state.counter.value); // state: RootState (autocomplete)
QACHON REDUX (Context/Zustand bilan taqqos):
┌──────────────┬─────────────────────────────────────────────┐
│ Context │ kam o'zgaradigan global (theme/auth — 12.1) │
│ Zustand │ oddiy global state (yengil, kam boilerplate) │
│ REDUX (RTK) │ KATTA, murakkab, jamoaviy, DevTools/middleware│
│ RTK/TanStack │ SERVER ma'lumoti (12.3/12.4 — Redux EMAS) │
└──────────────┴─────────────────────────────────────────────┘
RTK + TypeScript — typed hooks (useAppSelector/useAppDispatch — type-safe)
Redux — katta/murakkab/jamoaviy; kichik Zustand/Context; server QueryTypeScript bilan RTK va qachon Redux — yakuniy ikki mavzu. Typed hooks (RTK + TypeScript — 11.14): store turlarini chiqarasiz (
type RootState = ReturnType<typeof store.getState>,type AppDispatch = typeof store.dispatch), va typed hooklar yaratasiz (useAppDispatch,useAppSelector) — har joyda turni qayta yozmaslik uchun. KomponentdauseAppSelector((state) => state.counter.value)—stateto'liq turlangan (autocomplete, type-safe). Qachon Redux (eng muhim qaror — Context/Zustand bilan taqqos): Context — kam o'zgaradigan global (theme/auth — 12.1); Zustand — oddiy global state (yengil, kam kod — 12.5); Redux (RTK) — katta, murakkab, jamoaviy ilova (ko'p bog'liq state, DevTools/middleware ehtiyoji, qat'iy struktura); RTK Query/TanStack Query — server ma'lumoti (12.3/12.4 — Redux state'da emas). Ikki yakuniy nuqta: (1) RTK + TypeScript — typed hooks (useAppSelector/useAppDispatch) bilan to'liq type-safe; (2) Redux — katta/murakkab/jamoaviy ilova uchun (kichik Zustand/Context — kam boilerplate; server ma'lumoti Query). Eslatma: zamonaviy tendensiya — client state kam (ko'p ma'lumot server'dan — Query bilan), shuning uchun Redux ilgaridan kamroq ishlatiladi (lekin katta ilovalarda hali ham standart). To'g'ri vositani tanlash — tajribali dasturchining muhim ko'nikmasi.
2.12. Prepare callback, shallowEqual va listener middleware
PREPARE CALLBACK — action payload'ini reducer'dan OLDIN tayyorlash (id/timestamp):
reducers: {
addTodo: {
reducer: (state, action) => { state.push(action.payload); }, // sof reducer
prepare: (text) => ({ payload: { id: nanoid(), text, done: false } }), // payload tayyorlash
},
}
// dispatch(addTodo("Non olish")) prepare id/done qo'shadi reducer'ga tayyor payload
SHALLOWEQUAL — useSelector obyekt qaytarganda ortiqcha render'ni to'xtatish:
import { shallowEqual, useSelector } from "react-redux";
// const { a, b } = useSelector(s => ({ a: s.a, b: s.b })); // har safar yangi obyekt render
const { a, b } = useSelector(s => ({ a: s.a, b: s.b }), shallowEqual); // sayoz solishtir render yo'q
// yoki: ikki alohida useSelector (har biri primitiv — yaxshiroq)
LISTENER MIDDLEWARE — action'ga "reaksiya" (side effect — thunk'ning yengil muqobili):
import { createListenerMiddleware } from "@reduxjs/toolkit";
const listener = createListenerMiddleware();
listener.startListening({
actionCreator: logout, // logout dispatch bo'lganda...
effect: async (action, api) => { // ...bu effekt ishlaydi
localStorage.removeItem("token"); // side effect (masalan tozalash)
api.dispatch(cartActions.clearCart()); // boshqa action ham chaqirish mumkin
},
});
// store'ga: middleware: (gd) => gd().prepend(listener.middleware)
prepare — payload'ni tayyorlash (id/timestamp — reducer sof qoladi)
shallowEqual — obyekt qaytaruvchi useSelector'da ortiqcha render'ni to'xtatadi
listener middleware — action'ga reaksiya (side effect) — thunk'ning zamonaviy muqobiliPrepare callback,
shallowEqualva listener middleware — RTK'ning uch qo'shimcha, lekin amalda tez-tez kerak bo'ladigan imkoniyati. (1) Prepare callback — action payload'ini reducer ishlashidan oldin tayyorlash usuli. Ko'pincha payload'gaid(masalannanoid()),timestampyoki boshqa hosilaviy maydonlar qo'shish kerak bo'ladi — buni reducer ichida qilish reducer'ni nosof qilardi (Date.now(), tasodifiy id — side effect). Yechim:reducers: { addTodo: { reducer, prepare } }—preparepayload'ni shakllantiradi ((text) => ({ payload: { id: nanoid(), text, done: false } })),reduceresa tayyor payload'ni sof tarzda qo'llaydi. Chaqirish o'zgarmaydi:dispatch(addTodo("Non olish")). (2)shallowEqual—useSelectoryangi obyekt qaytarganda (s => ({ a, b })) ortiqcha render muammosini hal qilishning yana bir yo'li (2.6, 2.10 bilan bir oilada):useSelectorikkinchi argument sifatida solishtirish funksiyasini qabul qiladi;shallowEqualobyektni sayoz (bir daraja) solishtiradi — maydonlar bir xil bo'lsa render bo'lmaydi. Muqobil (odatda afzal): bir nechta alohidauseSelector(har biri primitiv qaytaradi). (3) Listener middleware (createListenerMiddleware) — muayyan action'ga reaksiya qilish (side effect) uchun rasmiy vosita —redux-saga/redux-observablening yengil, rasmiy muqobili.startListening({ actionCreator, effect })— masalanlogoutdispatch bo'lgandalocalStorage'ni tozalash yoki boshqa action chaqirish.createAsyncThunkdan farqi: thunk — bitta amalni boshlash, listener — action'lar oqimiga reaksiya (debounce, "shu action'dan keyin buni bajar" mantiqi). Uch nuqta: (1)prepare— payload'ni tayyorlash (reducer sof qoladi); (2)shallowEqual— obyekt qaytaruvchi selektorda ortiqcha render'ni to'xtatadi; (3) listener middleware — action'ga reaksiya (thunk'ning zamonaviy muqobili). Bular — RTK'ni professional darajada ishlatishning qo'shimcha vositalari.
2.13. redux-persist va test
REDUX-PERSIST — store'ni localStorage'ga SAQLASH (sahifa yangilanganda holat qoladi):
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage"; // localStorage
const persistConfig = { key: "root", storage, whitelist: ["cart", "auth"] }; // qaysi slice
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = configureStore({
reducer: persistedReducer,
middleware: (gd) => gd({
serializableCheck: { ignoredActions: ["persist/PERSIST", "persist/REHYDRATE"] },
}),
});
export const persistor = persistStore(store);
// <PersistGate loading={null} persistor={persistor}><App/></PersistGate>
TEST — slice va thunk (reducer sof test oson):
// slice reducer testi (Immer natijasini tekshirish):
expect(counterReducer({ value: 0 }, increment())).toEqual({ value: 1 });
// thunk testi (fetch mock, holatlarni tekshirish):
const state = usersReducer(initial, fetchUsers.fulfilled([{ id: 1 }], "", undefined));
expect(state.items).toHaveLength(1);
redux-persist — holatni localStorage'ga saqlash (yangilanganda qoladi — whitelist bilan tanlab)
Reducer SOF test oson: (state, action) kutilgan state (mock kam kerak)redux-persist va test — Redux'ning ikki amaliy jihati. (1) redux-persist — store holatini localStorage (yoki boshqa saqlash) ga avtomatik saqlaydigan kutubxona: sahifa yangilanganda yoki brauzer yopilib ochilganda holat qayta tiklanadi (masalan savat, auth token).
persistReducer(config, rootReducer)— reducer'ni "persist"ga o'raydi;persistStore(store)— persistor yaratadi;whitelist(yokiblacklist) — qaysi slice'lar saqlanishini tanlaydi (hammasi emas — masalan faqatcart,auth). React tomonda<PersistGate>holat tiklanguncha kutadi. Muhim nuqta: persist action'lari (persist/PERSIST,persist/REHYDRATE) non-serializable ma'lumot uzatadi, shuning uchunserializableCheck.ignoredActionsga qo'shiladi (Misol 13'dagi naqsh — 2.8). (2) Test — Redux'ning bashoratliligining bevosita foydasi: reducer sof funksiya (11.6: 2.3) bo'lgani uchun testi juda oddiy — kirish state va action berasiz, kutilgan chiquvchi state'ni tekshirasiz (expect(counterReducer({ value: 0 }, increment())).toEqual({ value: 1 })), hech qanday mock, DOM yoki render kerak emas.createAsyncThunkuchunfetch'ni mock qilib,fulfilled/rejectedaction'larini reducer'ga to'g'ridan uzatib holatni tekshirasiz (yoki butun thunk'nistore.dispatchbilan integratsion sinash). Ikki nuqta: (1) redux-persist — holatni saqlash (yangilanganda qoladi —whitelistbilan tanlab, persist action'lariniserializableCheckda e'tiborsiz qoldirib); (2) sof reducer test oson (mock kam, tez, ishonchli) — bu Redux arxitekturasining muhim yutug'i. Bular — Redux ilovasini production'ga tayyorlashning ikki muhim bosqichi.
3. Sintaksis — tez ma'lumotnoma
SLICE 2.4-bob: createSlice({ name, initialState, reducers: { add: (s,a) => {...} } })
STORE 2.4-bob: configureStore({ reducer: { counter: counterReducer } })
PROVIDER 2.3-bob: <Provider store={store}><App/></Provider>
ACTION 2.4-bob: export const { increment } = slice.actions
SELECT 2.6-bob: const x = useSelector((s) => s.counter.value)
DISPATCH 2.6-bob: const d = useDispatch(); d(increment()) / d(addBy(5))
IMMER 2.5-bob: reducers: { add: (s,a) => { s.items.push(a.payload) } } // mutatsiya OK
THUNK 2.7-bob: createAsyncThunk("x/fetch", async () => (await fetch()).json())
extraReducers: builder => builder.addCase(t.pending/fulfilled/rejected)
ENTITY 2.9-bob: createEntityAdapter() — addOne/updateOne/removeOne + selectors
SELECTOR 2.10-bob: createSelector([inputSel], (input) => derived) // memoized
TYPED 2.11-bob: RootState = ReturnType<typeof store.getState>; useAppSelector
PREPARE 2.12-bob: reducers: { add: { reducer: (s,a)=>{...}, prepare: (x)=>({payload:...}) } }
SHALLOW 2.12-bob: useSelector(s => ({a,b}), shallowEqual) // obyekt selektor
LISTENER 2.12-bob: createListenerMiddleware(); l.startListening({ actionCreator, effect })
PERSIST 2.13-bob: persistReducer(cfg, rootReducer); persistStore(store) + <PersistGate>
TEST 2.13-bob: expect(reducer(state, action())).toEqual(nextState) // sof reducer4. Batafsil kod namunalari
Misol 1 — Counter slice (asosiy — 2.4, 2.5)
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface CounterState { value: number; }
const initialState: CounterState = { value: 0 };
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => { state.value += 1; }, // Immer — mutatsiya OK (2.5)
decrement: (state) => { state.value -= 1; },
incrementByAmount: (state, action: PayloadAction<number>) => { // typed payload
state.value += action.payload;
},
reset: (state) => { state.value = 0; },
},
});
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;
export default counterSlice.reducer;Misol 2 — Store sozlash (2.4, 2.11)
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../features/counter/counterSlice";
import cartReducer from "../features/cart/cartSlice";
export const store = configureStore({
reducer: {
counter: counterReducer,
cart: cartReducer,
},
// DevTools, thunk, immutability tekshiruvi — AVTOMATIK (2.4, 2.8)
});
export type RootState = ReturnType<typeof store.getState>; // typed (2.11)
export type AppDispatch = typeof store.dispatch;Misol 3 — Provider va typed hooks (2.3, 2.11)
// main.tsx
import { Provider } from "react-redux";
import { store } from "./app/store";
createRoot(document.getElementById("root")!).render(
<Provider store={store}> {/* store'ni ilovaga ulaydi */}
<App />
</Provider>
);
// app/hooks.ts — typed hooklar 11.14-bob:
import { useDispatch, useSelector, type TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "./store";
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;Misol 4 — Komponentda ishlatish (useSelector/useDispatch — 2.6)
import { useAppSelector, useAppDispatch } from "../app/hooks";
import { increment, decrement, incrementByAmount } from "./counterSlice";
function Counter() {
const count = useAppSelector((state) => state.counter.value); // o'qish (faqat counter o'zgarsa render)
const dispatch = useAppDispatch();
return (
<div>
<p>{count}</p>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
</div>
);
}Misol 5 — Cart slice (massiv — Immer — 2.5)
interface CartItem { id: string; name: string; price: number; qty: number; }
interface CartState { items: CartItem[]; }
const cartSlice = createSlice({
name: "cart",
initialState: { items: [] } as CartState,
reducers: {
addItem: (state, action: PayloadAction<CartItem>) => {
const existing = state.items.find((i) => i.id === action.payload.id);
if (existing) existing.qty += 1; // Immer — to'g'ridan o'zgartirish OK
else state.items.push({ ...action.payload, qty: 1 }); // push OK (2.5)
},
removeItem: (state, action: PayloadAction<string>) => {
state.items = state.items.filter((i) => i.id !== action.payload);
},
updateQty: (state, action: PayloadAction<{ id: string; qty: number }>) => {
const item = state.items.find((i) => i.id === action.payload.id);
if (item) item.qty = action.payload.qty;
},
clearCart: (state) => { state.items = []; },
},
});
export const { addItem, removeItem, updateQty, clearCart } = cartSlice.actions;
export default cartSlice.reducer;Misol 6 — createAsyncThunk (async — 2.7)
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
export const fetchUsers = createAsyncThunk("users/fetch", async (_, { rejectWithValue }) => {
try {
const res = await fetch("/api/users");
if (!res.ok) throw new Error("Server xatosi");
return await res.json(); // fulfilled payload
} catch (err) {
return rejectWithValue((err as Error).message); // rejected payload
}
});
interface UsersState { items: User[]; loading: boolean; error: string | null; }
const usersSlice = createSlice({
name: "users",
initialState: { items: [], loading: false, error: null } as UsersState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => { state.loading = true; state.error = null; })
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false; state.items = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false; state.error = action.payload as string;
});
},
});
export default usersSlice.reducer;
// Komponentda: useEffect(() => { dispatch(fetchUsers()); }, []);Misol 7 — Async thunk komponentda (loading/error — 2.7)
function UsersList() {
const { items, loading, error } = useAppSelector((state) => state.users);
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(fetchUsers()); // pending fulfilled/rejected (avtomatik)
}, [dispatch]);
if (loading) return <Spinner />;
if (error) return <p className="error">{error}</p>;
return <ul>{items.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}
// Lekin server ma'lumoti uchun RTK Query 12.3-bob ANCHA yaxshiroq (kesh/sync — 2.7)Misol 8 — Memoized selektor (createSelector — 2.10)
import { createSelector } from "@reduxjs/toolkit";
import type { RootState } from "../app/store";
// const total = useSelector(s => s.cart.items.reduce(...)) — har render yangi hisob
// memoized — faqat items o'zgarganda qayta hisob:
export const selectCartTotal = createSelector(
(state: RootState) => state.cart.items,
(items) => items.reduce((sum, item) => sum + item.price * item.qty, 0)
);
export const selectCartCount = createSelector(
(state: RootState) => state.cart.items,
(items) => items.reduce((sum, item) => sum + item.qty, 0)
);
// Komponentda:
const total = useAppSelector(selectCartTotal); // memoized (kerakli render)Misol 9 — createEntityAdapter (normalizatsiya — 2.9)
import { createEntityAdapter, createSlice } from "@reduxjs/toolkit";
const productsAdapter = createEntityAdapter<Product>(); // ID obyekt (normalize)
const productsSlice = createSlice({
name: "products",
initialState: productsAdapter.getInitialState({ loading: false }), // {ids, entities, loading}
reducers: {
addProduct: productsAdapter.addOne, // tayyor CRUD reducer (2.9)
updateProduct: productsAdapter.updateOne,
removeProduct: productsAdapter.removeOne,
setProducts: productsAdapter.setAll,
},
});
export default productsSlice.reducer;
// Avtomatik selektorlar:
export const { selectAll: selectAllProducts, selectById: selectProductById } =
productsAdapter.getSelectors((state: RootState) => state.products);
// O(1) qidiruv/yangilash (entities["id"]) — katta ro'yxat uchun (2.9)Misol 10 — Auth slice (login/logout — 2.4)
interface AuthState { user: User | null; token: string | null; }
const authSlice = createSlice({
name: "auth",
initialState: { user: null, token: localStorage.getItem("token") } as AuthState,
reducers: {
setCredentials: (state, action: PayloadAction<{ user: User; token: string }>) => {
state.user = action.payload.user;
state.token = action.payload.token;
localStorage.setItem("token", action.payload.token); // persist (side effect — odatda thunk'da)
},
logout: (state) => {
state.user = null; state.token = null;
localStorage.removeItem("token");
},
},
});
export const { setCredentials, logout } = authSlice.actions;
export default authSlice.reducer;Misol 11 — Custom middleware (logging — 2.8)
import { Middleware } from "@reduxjs/toolkit";
const loggerMiddleware: Middleware = (store) => (next) => (action) => {
console.group(action.type);
console.log("oldingi state:", store.getState());
const result = next(action); // keyingi middleware/reducer'ga uzat
console.log("yangi state:", store.getState());
console.groupEnd();
return result;
};
// store'ga qo'shish:
const store = configureStore({
reducer: { /* ... */ },
middleware: (getDefault) => getDefault().concat(loggerMiddleware), // default + custom
});
// Middleware — action oqimiga oraliq mantiq (logging, analytics — 2.8)Misol 12 — Slice'lar orasida bog'liqlik (extraReducers — 2.7)
// logout bo'lganda cart ham tozalansin (boshqa slice action'iga reaksiya):
const cartSlice = createSlice({
name: "cart",
initialState: { items: [] } as CartState,
reducers: { /* add, remove... */ },
extraReducers: (builder) => {
builder.addCase(logout, (state) => { // auth slice'ning logout action'iga
state.items = []; // logout savat tozalansin
});
},
});
// extraReducers — boshqa slice action'iga reaksiya (slice'lar koordinatsiyasi — 2.7)Misol 13 — To'liq store (ko'p slice — 2.2, 2.3)
import { configureStore } from "@reduxjs/toolkit";
import authReducer from "../features/auth/authSlice";
import cartReducer from "../features/cart/cartSlice";
import productsReducer from "../features/products/productsSlice";
export const store = configureStore({
reducer: {
auth: authReducer,
cart: cartReducer,
products: productsReducer,
},
middleware: (getDefault) =>
getDefault({
serializableCheck: { ignoredActions: ["persist/PERSIST"] }, // ba'zi action'larni e'tiborga olmaydi
}),
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// Feature-based: har slice o'z papkasida, store'da birlashadi (2.3)Misol 14 — Redux vs Context vs Zustand qaror (2.11)
// CLIENT STATE — qaysi vosita:
// 1. Kam o'zgaradigan global Context 12.1-bob:
// theme, locale, auth — Context yetadi (oddiy)
// 2. Oddiy global state Zustand (12.5 — kam boilerplate):
// const useStore = create((set) => ({ count: 0, inc: () => set(s => ({count: s.count+1})) }))
// 3. Katta/murakkab/jamoaviy Redux Toolkit (bu bob):
// ko'p slice, DevTools, middleware, qat'iy struktura
// SERVER STATE — Redux EMAS:
// 4. API ma'lumoti RTK Query 12.3-bob yoki TanStack Query 12.4-bob:
// kesh, fonda yangilash, qayta urinish — AVTOMATIK (Redux thunk'dan yaxshiroq)
// To'g'ri vosita: kamContext, oddiyZustand, kattaRedux, serverQuery (2.11, 12.1: 2.10)Misol 15 — prepare callback va listener middleware (2.12)
import { createSlice, nanoid, createListenerMiddleware } from "@reduxjs/toolkit";
// prepare — payload'ga id/timestamp qo'shish (reducer SOF qoladi):
const todosSlice = createSlice({
name: "todos",
initialState: [] as Todo[],
reducers: {
addTodo: {
reducer: (state, action: PayloadAction<Todo>) => { state.push(action.payload); },
prepare: (text: string) => ({
payload: { id: nanoid(), text, done: false, createdAt: Date.now() }, // hosilaviy maydonlar
}),
},
},
});
export const { addTodo } = todosSlice.actions;
// dispatch(addTodo("Non olish")) prepare id/createdAt/done qo'shadi
// listener middleware — logout'ga reaksiya (side effect — thunk'ning muqobili):
export const listenerMiddleware = createListenerMiddleware();
listenerMiddleware.startListening({
actionCreator: logout, // logout dispatch bo'lganda...
effect: async (_action, api) => { // ...bu effekt ishlaydi
localStorage.removeItem("token"); // side effect (tozalash)
api.dispatch(clearCart()); // boshqa action ham chaqirish mumkin
},
});
// store'ga: middleware: (gd) => gd().prepend(listenerMiddleware.middleware)
// prepare — payload tayyorlash; listener — action'ga reaksiya (2.12)Misol 16 — obyekt selektor va shallowEqual (2.12, 2.10)
import { shallowEqual } from "react-redux";
function UserBadge() {
// har safar yangi obyekt har render re-render:
// const { name, avatar } = useAppSelector(s => ({ name: s.auth.user?.name, avatar: s.auth.user?.avatar }));
// 1-yo'l: shallowEqual (sayoz solishtir — maydonlar bir xil bo'lsa render yo'q):
const { name, avatar } = useAppSelector(
(s) => ({ name: s.auth.user?.name, avatar: s.auth.user?.avatar }),
shallowEqual
);
// 2-yo'l (odatda afzal): alohida primitiv selektorlar:
// const name = useAppSelector(s => s.auth.user?.name);
// const avatar = useAppSelector(s => s.auth.user?.avatar);
return <div><img src={avatar} alt="" /> {name}</div>;
}
// Obyekt qaytaruvchi selektor shallowEqual yoki alohida selektor (ortiqcha render — 2.10)Misol 17 — redux-persist va slice/thunk test (2.13)
// --- redux-persist (holatni localStorage'ga saqlash) ---
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage"; // localStorage
const rootReducer = combineReducers({ cart: cartReducer, auth: authReducer });
const persistConfig = { key: "root", storage, whitelist: ["cart", "auth"] }; // faqat shu slice'lar
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: (gd) =>
gd({ serializableCheck: { ignoredActions: ["persist/PERSIST", "persist/REHYDRATE"] } }),
});
export const persistor = persistStore(store);
// React: <PersistGate loading={null} persistor={persistor}><App/></PersistGate>
// --- test (Vitest/Jest) — sof reducer mock kam, test oson ---
import { describe, it, expect } from "vitest";
import counterReducer, { increment, incrementByAmount } from "./counterSlice";
describe("counterSlice", () => {
it("increment 1 ga oshiradi", () => {
expect(counterReducer({ value: 0 }, increment())).toEqual({ value: 1 });
});
it("incrementByAmount payload qo'shadi", () => {
expect(counterReducer({ value: 2 }, incrementByAmount(5))).toEqual({ value: 7 });
});
});
// thunk holatlarini reducer'ga to'g'ridan uzatib tekshirish:
it("fetchUsers.fulfilled items'ni to'ldiradi", () => {
const initial = { items: [], loading: true, error: null };
const next = usersReducer(initial, fetchUsers.fulfilled([{ id: "1", name: "Ali" }], "req"));
expect(next.loading).toBe(false);
expect(next.items).toHaveLength(1);
});
// redux-persist — holat saqlash (whitelist); reducer sof test oddiy (2.13)5. To'g'ri va noto'g'ri holatlar
1) Redux versiyasi
qo'lda Redux (action type konstanta, switch reducer — boilerplate)
Redux Toolkit (createSlice — avtomatik — 2.4)2) Immutability
createSlice tashqarisida mutatsiya (Immer yo'q — buziladi — 2.5)
createSlice ICHIDA mutatsiya OK (Immer); tashqarida yangi nusxa3) Selektor (hosilangan)
useSelector(s => s.items.filter(...)) (har render yangi massiv — 2.10)
createSelector (memoized)4) Server ma'lumoti
createAsyncThunk + slice'da API ma'lumoti saqlash (qo'lda kesh — 12.1: 2.10)
RTK Query / TanStack Query (avtomatik kesh — 12.3, 12.4)5) Hamma narsa Redux'da
har kichik UI holati Redux'da (boilerplate, ortiqcha)
lokal useState; global Redux (faqat kerakli — 2.1)6) Type
useSelector((s: any) => ...) (type yo'q)
useAppSelector (typed — RootState — 2.11)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — A non-serializable value was detected in the state
Sababi: state'da Promise/funksiya/Date saqlandi (2.8 serializability). Yechimi: faqat serializable (oddiy obyekt/massiv); Date — string sifatida; thunk natijasini to'g'rila.
Xato 2 — Komponent yangilanmaydi (state o'zgardi)
Sababi: mutatsiya createSlice tashqarisida, yoki yangi havola yo'q 2.5-bob. Yechimi: createSlice ICHIDA o'zgartir (Immer); selektor to'g'ri qism.
Xato 3 — Komponent har render re-render (sekin)
Sababi: useSelector yangi obyekt qaytaradi 2.10-bob. Yechimi: createSelector (memoized); yoki alohida primitiv selektor.
Xato 4 — Cannot find module 'react-redux' / Provider yo'q
Sababi: Provider o'ralmagan, yoki react-redux o'rnatilmagan. Yechimi: npm i @reduxjs/toolkit react-redux; <Provider store={store}> (Misol 3).
Xato 5 — Async thunk loading boshqarilmaydi
Sababi: extraReducers'da pending/fulfilled/rejected yo'q 2.7-bob. Yechimi: uchchovini ham boshqar (Misol 6).
Xato 6 — Redux DevTools ko'rinmaydi
Sababi: brauzer kengaytmasi yo'q yoki production. Yechimi: Redux DevTools kengaytmasini o'rnat (RTK avtomatik ulaydi — dev'da — 2.8).
Xato 7 — Hamma joyda type any
Sababi: typed hooks yo'q 2.11-bob. Yechimi: useAppSelector/useAppDispatch (RootState/AppDispatch — Misol 3).
Xato 8 — redux-persist bilan A non-serializable value ogohlantirishi
Sababi: persist ichki action'lari (persist/PERSIST, persist/REHYDRATE) non-serializable ma'lumot uzatadi 2.13-bob. Yechimi: serializableCheck.ignoredActionsga persist action'larini qo'sh (Misol 17, 2.13).
Xato 9 — Reducer ichida Date.now()/Math.random() (test beqaror)
Sababi: reducer nosof bo'lib qoladi — bir xil action har xil natija 2.12-bob. Yechimi: hosilaviy qiymatlarni prepare callback'da tayyorla (id, timestamp — reducer sof qoladi — Misol 15, 2.12).
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- Context + useReducer 12.1-bob: Redux — uning kengaytmasi (markaziy reducer + selektor).
- RTK Query 12.3-bob: RTK ustiga qurilgan server-state vositasi (data fetching).
- TanStack Query 12.4-bob: server state — Redux'dan ajratilgan (client vs server).
- Zustand 12.5-bob: oddiy global state — Redux'ning yengil muqobili.
- useReducer 11.6-bob: slice reducer — useReducer'ning Redux versiyasi (Immer bilan).
- TypeScript 11.14-bob: typed hooks, PayloadAction, RootState.
- Performance 11.11-bob: selektor (createSelector, shallowEqual) — re-render optimizatsiyasi.
- Backend (8-QISM): thunk — API'ga ulanadi (lekin Query afzal — 12.3).
- Testlash: sof reducer oddiy birlik test (mock kam — 2.13).
8. Eng yaxshi amaliyotlar (best practices)
- Redux Toolkit (qo'lda Redux emas) — createSlice/configureStore 2.4-bob.
- Immer createSlice ichida (mutatsiya sintaksisi — tashqarida yangi nusxa — 2.5).
- Typed hooks (useAppSelector/useAppDispatch — type-safe — 2.11).
- createSelector hosilangan qiymatga (memoized — re-render — 2.10).
- Server ma'lumotini Query'da (RTK/TanStack Query — Redux thunk emas — 12.1: 2.10).
- Faqat global state Redux'da (lokal useState — 2.1).
- Feature-based slice'lar (har feature o'z papkasida — 2.3).
- createEntityAdapter ro'yxatga (normalize — O(1) — 2.9).
- createAsyncThunk uch holat (pending/fulfilled/rejected — 2.7).
- DevTools'dan foydalanish (vaqt sayohati — debugging — 2.8).
- prepare callback hosilaviy payload'ga (id/timestamp — reducer sof — 2.12).
- Side effect'ga listener middleware (
createListenerMiddleware— action reaksiyasi — 2.12). - Kerakli slice'ni redux-persist bilan saqlash (whitelist — 2.13).
- Slice/thunk'ni test yozib qoplash (sof reducer — 2.13).
9. Amaliy loyiha: "E-commerce Holat Boshqaruvi (Redux Toolkit)"
Redux Toolkit'ni real, murakkab holatda mustahkamlash.
Maqsad
E-commerce ilovasi uchun to'liq Redux store yarat: auth, cart, products (entity), wishlist — slice'lar, thunk, selektorlar bilan.
Talablar (requirements)
- Store: configureStore + ko'p slice (auth/cart/products — Misol 2, 13).
- Typed hooks: useAppSelector/useAppDispatch (Misol 3, 2.11).
- Cart slice: add/remove/updateQty/clear (Immer — Misol 5).
- createSelector: cart total/count (memoized — Misol 8, 2.10).
- createAsyncThunk: mahsulotlarni API'dan ol (pending/fulfilled/rejected — Misol 6, 7).
- createEntityAdapter: products normalize (O(1) — Misol 9, 2.9).
- Slice koordinatsiyasi: logout cart tozalansin (extraReducers — Misol 12).
- Auth slice: setCredentials/logout + persist (Misol 10).
- DevTools: vaqt sayohati bilan tekshir (action oqimi — 2.8).
- Provider: ilovani Provider bilan o'ra (Misol 3).
Maslahatlar (hint)
- createSlice ICHIDA mutatsiya (Immer — tashqarida emas — Xato 2).
- Hosilangan qiymat (total) — createSelector (har render emas — Misol 8).
- Server ma'lumoti uchun thunk ishlaydi, lekin keyingi bobda RTK Query yaxshiroq 2.7-bob.
- Typed hooks — har joyda useAppSelector (any emas — Xato 7).
- DevTools bilan har action'ni kuzat (debugging — 2.8).
- Slice'larni feature papkalariga ajrat 2.3-bob.
"Tayyor" mezonlari (acceptance criteria)
- Store + ko'p slice (auth/cart/products).
- Typed hooks (useAppSelector/useAppDispatch).
- Cart CRUD (Immer bilan).
- createSelector (total/count memoized).
- createAsyncThunk (3 holat).
- createEntityAdapter (products normalize).
- Slice koordinatsiyasi (logout cart).
- DevTools'da action oqimi ko'rinadi.
- Provider o'ralgan, ilova ishlaydi.
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda Redux Toolkit'ni chuqur o'rgandik:
- Nega Redux (Context cheklovi — 2.1); asosiy tushunchalar (store/action/reducer — Flux — 2.2); qatlamlar 2.3-bob; configureStore/createSlice (RTK — 2.4); Immer 2.5-bob.
- useSelector/useDispatch (tabiiy selektor — 2.6); createAsyncThunk (async — 2.7); middleware/DevTools 2.8-bob; createEntityAdapter (normalize — 2.9).
- createSelector (memoized — 2.10); TypeScript va qachon Redux 2.11-bob.
- prepare callback, shallowEqual, listener middleware 2.12-bob; redux-persist va test 2.13-bob.
Endi siz katta, murakkab ilova holatini Redux Toolkit bilan markaziy, bashoratli, kuzatiladigan tarzda boshqara olasiz — slice, thunk, selektor, DevTools. Eng muhimi — client state (Redux) va server state (Query) farqini bilasiz.
Keyingi bob — 12.3-bob: RTK Query. Redux'da server ma'lumotini createAsyncThunk bilan boshqarish mumkin, lekin u ko'p qo'lda kod (loading/error/kesh) talab qiladi. RTK Query — Redux Toolkit'ning server-state yechimi (data fetching va caching): bir necha qator bilan API endpoint'larini belgilaysiz, va u kesh, fonda yangilash, qayta urinish, loading/error, deduplication — barchasini avtomatik qiladi. Bu — server ma'lumotini professional boshqarishning Redux ekotizimidagi yechimi (TanStack Query — 12.4 — bilan raqobatda).
Foydalanilgan rasmiy/ishonchli manbalar
- Redux Toolkit rasmiy hujjati —
configureStore,createSlice,createAsyncThunk,createEntityAdapter,createSelector,createListenerMiddleware, prepare callback, TypeScript qo'llanmasi - Redux rasmiy hujjati — "Redux Essentials" darsligi, rasmiy "Style Guide" (best practices), Flux/bir tomonlama oqim tamoyillari, typed hooks (Quick Start)
- React-Redux rasmiy hujjati —
Provider,useSelector,useDispatch,shallowEqual,TypedUseSelectorHook, re-render optimizatsiyasi - Immer rasmiy hujjati — "draft" mexanizmi, immutable natija, ishlatish chegaralari
- Reselect rasmiy hujjati — memoized selektor, input/output selektor, hosilangan holat
- redux-persist rasmiy hujjati —
persistReducer,persistStore,PersistGate, whitelist/blacklist - Redux DevTools — vaqt sayohati (time-travel), action va state inspeksiyasi
- Redux jamoasi maqolalari (Mark Erikson) — "When (and when not) to reach for Redux", RTK best practices, client vs server state
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!