Mundarija (24)
- Bu darsda
- 1. Nega bu kerak?
- 2. Flux oqimi
- 2.1 Uch qoida
- 2.2 Jonli model: Redux 20 qatorda
- 3. Redux Toolkit: createSlice va configureStore
- 3.1 Nega "Toolkit"?
- 3.2 createSlice — reducer va amallar birga
- 3.3 Immer: "o'zgartirib" yozish va muzlatilgan natija
- 3.4 configureStore tekshiruvlari
- 4. React'da: Provider, useSelector, useDispatch
- 4.1 Ulash
- 4.2 Tuzoq: selektor yangi obyekt qaytaradi
- 4.3 TypeScript: tiplangan hooklar
- 5. Kanon tajribasi: Redux Toolkit varianti (nega kanonga kirmadi)
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): natijani ayting
- 2-mashq (o'rta): band stollar
- 3-mashq (qiyin): o'quv modeliga combineReducers
- 4-mashq (tahlil): kanon tajribasini o'qing
- 8. Real ishda
- Xulosa
- Manbalar
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:createSlicereducer va amal yasovchilarni birga beradi (ichida Immer —state.items.push(x)deb yozasiz),configureStorestore'ni tayyor tekshiruvlar bilan yaratadi. React'da —<Provider store>,useSelector(o'qish) vauseDispatch(yuborish); TypeScript'da —useSelector.withTypes<RootState>().
Bu darsda
- Flux oqimini (amal → reducer → yangi holat → ekran) chizib tushuntira olasiz.
createSlicebilan slice yozasiz va u qaytaradigan amal yasovchilarni ishlatasiz.configureStoreqo'shadigan ikki tekshiruvni (o'zgartirish va serializatsiya) ko'rasiz.- React'da
Provider,useSelector,useDispatchbilan ishlaysiz va selektor tuzog'ini taniysiz. - TypeScript'da tiplangan hooklarni (
withTypes) yasaysiz. - Redux qaysi loyihada to'g'ri tanlov ekanini va nega
vazifalarda 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:
- Yagona haqiqat manbai. Butun global holat bitta store'da, bitta obyekt daraxtida.
- Holat faqat o'qish uchun. Uni o'zgartirishning yagona yo'li — amal yuborish:
dispatch({ type: "savat/added", payload: "Osh" }). - 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"| VDiagrammadagi 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:
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:
amal: savat/added Osh
amal: savat/added MantiEkranda:
Osh Manti
Savat: Osh, MantiUch narsaga qarang:
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.dispatch— yagona eshik: hamma o'zgarish shu yerdan o'tadi. Shuning uchun bittaconsole.logbutun ilovaning tarixini yozdi. Redux DevTools aynan shu nuqtaga ulanadi.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).
npm i --save-exact @reduxjs/toolkit@2.13.0 react-redux@9.3.03.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:
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:
{ 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'dagiadded—"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:
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:
[ 'Osh' ] [ 'Osh', 'Manti' ]
true
TypeError: Cannot add property 2, object is not extensibleEski 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):
- Immutability check — holat ikki
dispatchorasida joyida o'zgartirilganini ushlaydi. - 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:
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:
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 etdiTarjimasi: "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>:
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.dispatchfunksiyasini 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:
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:
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 chizildiIkkinchi 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:
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):
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 }. Undanaddedyasovchisi(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 odatdahooks.tsfaylida bir marta yoziladi va komponentlar faqatuseAppSelector/useAppDispatchni 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 koddawithTypesishlating.
Tekshirib ko'ring: Nega
RootStateturini 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):
- 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):
// 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):
vazifalarning 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.dispatchham 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.
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
{ 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:
name: "stollar", boshlang'ich holat{ busy: [] }.occupied(id)— stol band bo'ladi (bir stol ikki marta qo'shilmasin).freed(id)— stol bo'shaydi.- Store'da slice
stollarkalitida;subscribehar o'zgarishdaband:va ro'yxatni chiqarsin. - Ketma-ketlik: 4 band, 7 band, yana 4 band, 4 bo'shadi.
Ishora: massivdan element olib tashlash — filter; bor-yo'qligi — includes.
Yechim
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:
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:
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:
[]
["Osh"]Yechim
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:
{"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:
- Redux variantida
Korinishga berilganqosh— har renderda yangi funksiya. Zustand variantida-chi? - Sizning jamoangiz 30 kishi, ilova 5 yoshda va Redux'da. Yangi "sevimli taomlar" holati uchun Zustand qo'shasizmi?
Yechim
- Zustand variantida
qoshstore yaratilganda bir marta yasalgan — barqaror havola. Redux variantida(m) => d(qosh(m))har chizishda yangi o'q funksiya.Korinishmemobilan o'ralganda bu farq qilardi (memo, useMemo, useCallback). Redux'da odatdadispatchni to'g'ridan-to'g'ri komponentda chaqirasiz, shuning uchun bunday o'rovchi kerak bo'lmaydi. - 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 koddaconnect(mapStateToProps)(Component)ni ko'rasiz — bu HOC naqshi, hooklardan oldingi usul. Ma'nosiuseSelector+useDispatchbilan bir xil.- Redux DevTools — amallar tarixi, holat farqi, time travel. RTK'da
configureStoreuni o'zi ulaydi (dev rejimida). - Intervyu savollari: "Flux oqimini tushuntiring", "reducer nega sof bo'lishi kerak?", "
createSliceichidastate.pushqilsa bo'ladimi — nega?", "Redux va Zustand — qachon qaysi?", "useSelectorqachon 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 turislice/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/AppDispatchstore'dan chiqariladi,useSelector.withTypesbilan tiplangan hooklar. - Tajriba: bir xil vazifa uchun RTK +8.3 KB va 34 qator, Zustand +0.4 KB va 14 qator —
vazifalarda 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
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!