Mundarija (24)
- Bu darsda
- 1. Nega bu kerak?
- 2. Async amallar: thunk
- 2.1 Thunk g'oyasi — jonli model
- 2.2 createAsyncThunk va extraReducers
- 2.3 O'z middleware'ingiz: logger
- 3. createSelector: eslab qoladigan selektor
- 3.1 Muammo: har safar yangi natija
- 3.2 RTK'dagi createSelector
- 4. Normalizatsiya: createEntityAdapter
- 4.1 Tekis tuzilma
- 4.2 createEntityAdapter
- 5. Listener middleware: amalga javob
- 6. Redux DevTools
- 7. Kanon tajribasi: server state — TanStack Query'da
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): uch amal
- 2-mashq (o'rta): issiq taomlar selektori
- 3-mashq (qiyin): buyurtmalar paneli
- 4-mashq (tahlil): thunk yoki Query?
- 10. Real ishda
- Xulosa
- Manbalar
Redux Toolkit async: createAsyncThunk, createSelector, createEntityAdapter va listener
Qisqacha: Reducer sof bo'lgani uchun serverga so'rov unda yozilmaydi. Async ish — thunk da:
createAsyncThunk("menyu/fetch", async () => …)o'zi uch amal yuboradi —pending,fulfilled,rejected. Slice ularniextraReducersda ushlab,statusni yangilaydi. Selektor uchuncreateSelectorhisoblangan qiymatni eslab qoladi: kirishlar o'zgarmasa, eski natija (o'sha havola) qaytadi.createEntityAdapterro'yxatni{ ids, entities }ko'rinishida (normalizatsiya) saqlaydi va tayyor CRUD reducer va selektorlar beradi. Listener middleware — "shu amal bo'lsa, buni qil" degan yon ta'sirlar uchun.
Bu darsda
createAsyncThunkvaextraReducersbilan yuklash holatini (yuklanmoqda,tayyor,xato) boshqarasiz.- O'z middleware'ingizni yozib, har amalni jurnalga chiqarasiz.
createSelectorqachon qayta hisoblashini va qachon eski natijani qaytarishini o'lchaysiz.- Ro'yxatni
createEntityAdapterbilan normalizatsiya qilasiz. - Listener middleware bilan amalga javob beradigan effekt yozasiz.
- Nega
vazifalarda server state baribir TanStack Query'da ekanini bilasiz.
Oldin bilishingiz kerak: Redux Toolkit I, Effektda ma'lumot olish, TanStack Query I, State strukturasini loyihalash.
1. Nega bu kerak?
O'tgan darsda «Bahor» savati Redux'da ishladi. Endi menyuni serverdan olish kerak. Sardor birinchi bo'lib reducer ichida fetch yozmoqchi bo'ldi — va to'xtadi: reducer sof bo'lishi shart. U faqat (holat, amal) => yangiHolat hisoblaydi, tarmoqqa chiqmaydi, kutmaydi.
Ikkinchi muammo: menyuda 300 taom. "Osh narxini yangila" — massivni boshidan oxirigacha aylanib, id bo'yicha topish. Savatda ham, buyurtmalarda ham shu taom nusxasi bor — narx bir joyda yangilanib, boshqasida eski qoladi.
Uchinchisi: savat sahifasida "jami summa" har chizishda qaytadan hisoblanadi, yangi massiv yasaydigan filtr esa o'tgan darsdagi "Selector returned a different result" ogohlantirishini beradi.
Bugungi to'rt vosita shu uch muammoga javob. Hayotiy o'xshatish: restoran ombori (inglizchasi — store, Redux'dagi o'sha so'z). Mahsulot ro'yxati (ids) alohida, har mahsulotning kartochkasi (entities) alohida javonda — raqami bo'yicha darhol topiladi. Yetkazib beruvchi bilan gaplashish (async) — omborchining emas, alohida xodimning ishi. Omborchi faqat "yuk keldi" (fulfilled) yoki "kelmadi" (rejected) degan qog'ozni oladi.
2. Async amallar: thunk
2.1 Thunk g'oyasi — jonli model
Thunk — keyinroq bajariladigan ishni o'rab turgan funksiya. Redux'da: oddiy obyekt o'rniga dispatch ga funksiya yuborasiz. Maxsus middleware (redux-thunk, RTK'da sukut bo'yicha ulangan) uni ko'rib, chaqirib qo'yadi va unga dispatch va getState ni beradi. Funksiya ichida istalgan async ish bo'lishi mumkin, oddiy amallarni esa o'zi yuboradi.
Saytdagi natija oynasida Redux yo'q, shuning uchun g'oyani o'tgan darsdagi o'quv modelida ko'ramiz (bu Redux emas). Modelga bitta qator qo'shildi: amal funksiya bo'lsa — chaqir.
import { useSyncExternalStore } from "react";
function createStore(reducer) {
let state = reducer(undefined, { type: "@@boshlash" });
const listeners = new Set();
const store = {
getState: () => state,
dispatch(action) {
// thunk: amal o'rniga funksiya kelsa — uni chaqiramiz
if (typeof action === "function") {
return action(store.dispatch, store.getState);
}
console.log("amal:", action.type);
state = reducer(state, action);
listeners.forEach((listener) => listener());
},
subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
};
return store;
}
function menuReducer(state = { status: "bosh", dishes: [] }, a) {
switch (a.type) {
case "menyu/pending":
return { ...state, status: "yuklanmoqda" };
case "menyu/fulfilled":
return { status: "tayyor", dishes: a.payload };
default:
return state;
}
}
const store = createStore(menuReducer);
const fakeApi = () =>
new Promise((resolve) =>
setTimeout(() => resolve(["Osh", "Manti"]), 200),
);
// thunk: async ish va oddiy amallar
const fetchMenu = () => async (dispatch) => {
dispatch({ type: "menyu/pending" });
const dishes = await fakeApi();
dispatch({ type: "menyu/fulfilled", payload: dishes });
};
export default function App() {
const { status, dishes } = useSyncExternalStore(
store.subscribe,
store.getState,
);
return (
<>
<button onClick={() => store.dispatch(fetchMenu())}>
Menyuni yukla
</button>
<p>Holat: {status}</p>
<p>{dishes.join(", ")}</p>
</>
);
}Konsolda:
amal: menyu/pending
amal: menyu/fulfilledEkranda:
Menyuni yukla
Holat: tayyor
Osh, MantiBosilgan zahoti ekranda "yuklanmoqda" (tekshiruvchi buni oraliq nuqtada ko'rdi), 200 ms dan keyin — "tayyor". Reducer sof qoldi: u faqat pending va fulfilled obyektlarini talqin qildi. Tarmoq, kutish, await — hammasi fetchMenu thunk'ida.
2.2 createAsyncThunk va extraReducers
Modeldagi pending/fulfilled ni har so'rov uchun qo'lda yozish — zerikarli, rejected ni unutish oson. RTK'da buni createAsyncThunk qiladi:
import {
configureStore,
createAsyncThunk,
createSlice,
} from "@reduxjs/toolkit";
// soxta API: 50 ms dan keyin menyu (yoki xato)
const fakeApi = (fail) =>
new Promise((resolve, reject) =>
setTimeout(() => {
if (fail) reject(new Error("Server javob bermadi"));
else resolve([{ id: 1, name: "Osh", price: 35000 }]);
}, 50),
);
const fetchMenu = createAsyncThunk("menyu/fetch", async (fail) => {
return await fakeApi(fail);
});
const menuSlice = createSlice({
name: "menyu",
initialState: { dishes: [], status: "bosh", error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchMenu.pending, (state) => {
state.status = "yuklanmoqda";
state.error = null;
})
.addCase(fetchMenu.fulfilled, (state, action) => {
state.status = "tayyor";
state.dishes = action.payload;
})
.addCase(fetchMenu.rejected, (state, action) => {
state.status = "xato";
state.error = action.error.message;
});
},
});
const logger = (api) => (next) => (action) => {
console.log("amal:", action.type);
return next(action);
};
const store = configureStore({
reducer: { menyu: menuSlice.reducer },
middleware: (getDefault) => getDefault().concat(logger),
});
const result = await store.dispatch(fetchMenu(false));
console.log(store.getState().menyu);
console.log("natija turi:", result.type);
await store.dispatch(fetchMenu(true));
const { status, error } = store.getState().menyu;
console.log(status, error);Toza papkada @reduxjs/toolkit@2.13.0 bilan Node 24 da:
amal: menyu/fetch/pending
amal: menyu/fetch/fulfilled
{
dishes: [ { id: 1, name: 'Osh', price: 35000 } ],
status: 'tayyor',
error: null
}
natija turi: menyu/fetch/fulfilled
amal: menyu/fetch/pending
amal: menyu/fetch/rejected
xato Server javob bermadiNimalar bo'ldi:
createAsyncThunk("menyu/fetch", payloadCreator)— thunk yasovchi. Payload creator — sizning async funksiyangiz: u qaytargan qiymatfulfilledamaliningpayloadiga, tashlagan xatosi —rejectedga tushadi.- Amal turlari avtomatik:
menyu/fetch/pending,…/fulfilled,…/rejected. Bu nomlarfetchMenu.pendingkabi xususiyatlarda ham bor —addCaseularni oladi. extraReducers— slice'ning o'zi yaratmagan amallarga javobi.reducersdagilar slice'ning shaxsiy amallari,extraReducers— tashqaridan (thunk yoki boshqa slice'dan) keladiganlar. O'tgan darsdagi "amal — voqea" g'oyasi:fetchMenu.fulfilledga ham menyu, ham, masalan, "oxirgi yangilanish vaqti" slice'i javob berishi mumkin.await store.dispatch(fetchMenu())— thunk promise qaytaradi; natijasi — oxirgi amalning o'zi (result.type).statusmaydoni — Reducer + Immer va holat mashinasi darsidagi "boolean'lar emas, bittastatus" qoidasi:yuklanmoqdavaxatobir vaqtda rost bo'la olmaydi.
sequenceDiagram
participant C as Komponent
participant T as fetchMenu (thunk)
participant A as API
participant R as menyu reducer
C->>T: dispatch(fetchMenu())
T->>R: menyu/fetch/pending
T->>A: so'rov
A-->>T: javob yoki xato
T->>R: fulfilled (payload) yoki rejected (error)
R-->>C: status: tayyor / xato2.3 O'z middleware'ingiz: logger
Misoldagi logger — middleware ning to'liq anatomiyasi. Uch qavatli funksiya: (api) => (next) => (action) => …. api — getState va dispatch; next — zanjirdagi keyingi middleware (oxirida reducer); action — kelgan amal. next(action) ni chaqirmasangiz, amal reducer'ga yetmaydi.
getDefault().concat(logger) — RTK'ning standart middleware'lari (thunk, o'tgan darsdagi ikki tekshiruv) oxiriga biznikini qo'shadi. Thunk birinchi turgani uchun funksiya-amal logger ga yetmaydi: jurnalda faqat haqiqiy amallar.
Maslahat: bekor qilish ham bor.
createAsyncThunkpayload creator'ning ikkinchi argumentidasignal(AbortController) beradi,dispatch(fetchMenu())qaytargan promise'da esa.abort()metodi. Komponent yopilganda effekt tozalash funksiyasidapromise.abort()— eski so'rov natijasi kech kelib holatni buzmaydi.
Tekshirib ko'ring: Payload creator
return fetch(url)deb, javobni.json()siz qaytarsa,fulfilledningpayloadida nima bo'ladi va qaysi ogohlantirish chiqadi?
Javob
payload — Response obyekti, ma'lumot emas. U serializatsiya qilinmaydi, shuning uchun o'tgan darsdagi A non-serializable value was detected in an action, in the path: \payload`ogohlantirishi chiqadi. To'g'risi:const res = await fetch(url); return await res.json();— vares.ok` ni tekshirish (fetch asoslari).
3. createSelector: eslab qoladigan selektor
3.1 Muammo: har safar yangi natija
useSelector((s) => s.dishes.filter((d) => d.hot)) — har chaqiruvda yangi massiv. O'tgan darsda ko'rdik: react-redux ogohlantiradi va komponent har dispatch da chiziladi. Hisob og'ir bo'lsa (300 taom, saralash), bu sekinlik ham beradi.
Memoizatsiya (Memoization) — oxirgi kirish va natijani eslab qolish: kirish o'sha bo'lsa, hisoblamay, eski natijani qaytarish. Selektor uchun bu shunday ko'rinadi (kutubxonasiz o'quv modeli, ishga tushiring):
function createSelector(inputs, compute) {
let lastArgs = null;
let lastResult;
return (state) => {
const args = inputs.map((select) => select(state));
const same =
lastArgs !== null && args.every((a, i) => a === lastArgs[i]);
if (!same) {
lastArgs = args;
lastResult = compute(...args);
}
return lastResult;
};
}
let runs = 0;
const selectHot = createSelector([(s) => s.dishes], (dishes) => {
runs += 1;
return dishes.filter((d) => d.hot);
});
const s1 = { dishes: [{ name: "Osh", hot: true }], table: 4 };
const s2 = { ...s1, table: 5 };
console.log(selectHot(s1) === selectHot(s2)); // true
console.log(runs); // 1s2 — yangi obyekt (stol o'zgardi), lekin s2.dishes — o'sha havola. Kirish o'zgarmadi — hisob bir marta, natija — o'sha massiv. useSelector uchun bu "o'zgarmadi" degani: chizish yo'q.
3.2 RTK'dagi createSelector
RTK createSelector ni Reselect kutubxonasidan qayta eksport qiladi (5.3.0, RTK bilan birga keladi). Ishlashi modeldagidek, lekin puxtaroq (masalan, dev rejimida kirish selektori har safar yangi qiymat qaytarsa, ogohlantiradi):
import { createSelector } from "@reduxjs/toolkit";
let computed = 0;
const selectItems = (state) => state.savat.items;
const selectTotal = createSelector([selectItems], (items) => {
computed += 1;
return items.reduce((sum, item) => sum + item.price, 0);
});
const s1 = {
savat: { items: [{ name: "Osh", price: 35000 }] },
waiter: "Sardor",
};
console.log(selectTotal(s1), "hisoblandi:", computed);
console.log(selectTotal(s1), "hisoblandi:", computed);
const s2 = { ...s1, waiter: "Malika" };
console.log(selectTotal(s2), "hisoblandi:", computed);
const s3 = {
...s2,
savat: {
items: [...s2.savat.items, { name: "Choy", price: 5000 }],
},
};
console.log(selectTotal(s3), "hisoblandi:", computed);Node 24, RTK 2.13.0:
35000 hisoblandi: 1
35000 hisoblandi: 1
35000 hisoblandi: 1
40000 hisoblandi: 2Ofitsiant almashdi (s2) — savat o'sha, hisob yo'q. Savatga choy qo'shildi (s3) — yangi items massivi, hisob ikkinchi marta. Ikki qism: kirish selektorlari (selectItems — arzon, faqat qiymatni oladi) va natija funksiyasi (og'ir ish). Kirish selektorlari ichida hech qachon filter/map qilmang — ular har safar yangi massiv qaytarib, memoizatsiyani buzadi.
Komponentda: const total = useSelector(selectTotal); — selektor modul darajasida bir marta yaratiladi. Komponent ichida createSelector chaqirilsa, har chizishda yangi (bo'sh xotirali) selektor yasaladi va foydasi yo'qoladi.
Tekshirib ko'ring:
createSelector([(s) => s.dishes.filter((d) => d.hot)], (hot) => hot.length)— nima noto'g'ri?
Javob
filter kirish selektorida: u har chaqiruvda yangi massiv beradi, shuning uchun natija funksiyasi har safar qayta ishlaydi — memoizatsiya hech qachon ishlamaydi. To'g'risi: kirish — (s) => s.dishes, filter va length — natija funksiyasida. Bu yerda natija son bo'lgani uchun xavfli emas, lekin ish bekorga takrorlanadi.
4. Normalizatsiya: createEntityAdapter
4.1 Tekis tuzilma
State strukturasini loyihalash darsida normalizatsiya (tekis tuzilma) ni ko'rgan edik: har yozuv bir joyda, id bo'yicha; boshqa joylar faqat id ni saqlaydi. Massivdan tekis tuzilmaga o'tish — kutubxonasiz, ishga tushiring:
const dishes = [
{ id: 7, name: "Osh", price: 35000 },
{ id: 3, name: "Manti", price: 30000 },
];
function normalize(list) {
return {
ids: list.map((item) => item.id),
entities: Object.fromEntries(list.map((item) => [item.id, item])),
};
}
const state = normalize(dishes);
console.log(state.ids); // [ 7, 3 ]
console.log(state.entities[3].name); // Mantientities[3] — massivni aylanmasdan, darhol. ids — tartib (ro'yxatni chizish uchun). Savatda esa taom nusxasi emas, faqat id saqlanadi — narx bitta joyda yangilanadi.
4.2 createEntityAdapter
Har ro'yxat uchun addOne, updateOne, removeOne ni qo'lda yozish — takroriy ish. Entity adapter ularni tayyor beradi:
import {
configureStore,
createEntityAdapter,
createSlice,
} from "@reduxjs/toolkit";
const dishesAdapter = createEntityAdapter({
sortComparer: (a, b) => a.name.localeCompare(b.name),
});
const dishesSlice = createSlice({
name: "taomlar",
initialState: dishesAdapter.getInitialState(),
reducers: {
dishesLoaded: dishesAdapter.setAll,
dishUpdated: dishesAdapter.updateOne,
dishRemoved: dishesAdapter.removeOne,
},
});
const { dishesLoaded, dishUpdated, dishRemoved } =
dishesSlice.actions;
const store = configureStore({
reducer: { taomlar: dishesSlice.reducer },
});
store.dispatch(
dishesLoaded([
{ id: 7, name: "Osh", price: 35000 },
{ id: 3, name: "Manti", price: 30000 },
{ id: 9, name: "Lag'mon", price: 28000 },
]),
);
console.log(store.getState().taomlar);
store.dispatch(dishUpdated({ id: 7, changes: { price: 38000 } }));
store.dispatch(dishRemoved(3));
const selectors = dishesAdapter.getSelectors((s) => s.taomlar);
const state = store.getState();
console.log(selectors.selectIds(state));
console.log(selectors.selectById(state, 7));
console.log(selectors.selectTotal(state));Node 24, RTK 2.13.0:
{
ids: [ 9, 3, 7 ],
entities: {
'3': { id: 3, name: 'Manti', price: 30000 },
'7': { id: 7, name: 'Osh', price: 35000 },
'9': { id: 9, name: "Lag'mon", price: 28000 }
}
}
[ 9, 7 ]
{ id: 7, name: 'Osh', price: 38000 }
2getInitialState()—{ ids: [], entities: {} }. Unga qo'shimcha maydon ham berish mumkin:getInitialState({ status: "bosh" }).setAll,addOne,addMany,updateOne({ id, changes }),upsertOne,removeOne— tayyor reducerlar. To'g'ridan-to'g'rireducersga qo'yiladi yoki boshqa reducer ichida chaqiriladi:dishesAdapter.addMany(state, action.payload).sortComparer—idstartibi: nom bo'yicha ("Lag'mon", "Manti", "Osh").entitiesdagi kalitlar tartibi muhim emas: obyekt kalitlari son bo'lsa, JavaScript ularni o'sish tartibida chiqaradi.getSelectors—selectAll,selectById,selectIds,selectTotal.selectAllmemoizatsiyalangan: tartiblangan massiv faqatidsyokientitieso'zgarganda qayta yasaladi.
Diqqat:
entitiesdagi kalitlar — satr ('7'), garchiidson bo'lsa ham. JavaScript obyekt kalitlari doim satr.entities[7]ham ishlaydi (son satrga aylanadi), lekinObject.keys(entities)—['3', '7', '9'], sonlar emas.idsifatida doimidsmassividan foydalaning.
5. Listener middleware: amalga javob
Ba'zi ishlar reducer'ga ham, komponentga ham to'g'ri kelmaydi: "savatga taom tushganda — summa 60 000 dan oshsa, oshpazga xabar". Bu — yon ta'sir (side effect), uni amal keltirib chiqaradi. RTK'da listener middleware: "shu amal kelsa, shu effektni ishga tushir".
import {
configureStore,
createListenerMiddleware,
createSlice,
} from "@reduxjs/toolkit";
const cartSlice = createSlice({
name: "savat",
initialState: { items: [], notice: "" },
reducers: {
added(state, action) {
state.items.push(action.payload);
},
noticed(state, action) {
state.notice = action.payload;
},
},
});
const { added, noticed } = cartSlice.actions;
const listener = createListenerMiddleware();
listener.startListening({
actionCreator: added,
effect: async (action, api) => {
const { items } = api.getState().savat;
const total = items.reduce((s, i) => s + i.price, 0);
console.log(`effekt: ${action.payload.name}, jami ${total}`);
if (total > 60000) {
api.dispatch(noticed("Katta buyurtma — oshpazga xabar"));
}
},
});
const store = configureStore({
reducer: { savat: cartSlice.reducer },
middleware: (getDefault) =>
getDefault().prepend(listener.middleware),
});
store.dispatch(added({ name: "Osh", price: 35000 }));
store.dispatch(added({ name: "Manti", price: 30000 }));
await new Promise((r) => setTimeout(r, 0));
console.log(store.getState().savat.notice);Node 24, RTK 2.13.0:
effekt: Osh, jami 35000
effekt: Manti, jami 65000
Katta buyurtma — oshpazga xabarEffekt reducer'dan keyin ishlaydi — api.getState() allaqachon yangi holatni beradi (ikkinchi qatorda manti hisobga olingan). Effekt ichida await, boshqa amal yuborish, taymer — hammasi mumkin. prepend — listener'ni zanjir boshiga qo'yish (hujjat tavsiyasi). Murakkabroq oqimlar uchun api.take() (keyingi amalni kutish), api.delay(), api.cancelActiveListeners() (eski effektni bekor qilish — debounce uchun) bor.
Bu — Zustand I dagi subscribe ning kuchliroq varianti: holatning har o'zgarishiga emas, aniq amalga quloq soladi va amal ma'lumotini (action.payload) oladi. Eski Redux kodida xuddi shu ish uchun redux-saga yoki redux-observable ni ko'rasiz — listener middleware ularning RTK ichidagi yengil o'rinbosari.
Tekshirib ko'ring: Nega "katta buyurtma" xabarini
addedreducerining ichidastate.notice = …qilib yozish mumkin, lekin oshpazga Telegram xabar yuborishni — yo'q?
Javob
notice ni hisoblash — sof ish: holatdan holat. Uni reducer'da qilsa bo'ladi (aslida bu sodda holatda shunday qilish ham to'g'ri). Telegram xabari esa tashqi tizimga so'rov — yon ta'sir: reducer qayta o'ynatilganda (DevTools time travel) xabar qayta ketardi, testda tarmoqqa chiqardi. Yon ta'sirlar — listener yoki thunk'da.
6. Redux DevTools
configureStore dev rejimida Redux DevTools kengaytmasiga o'zi ulanadi (Zustand II da ko'rgan kengaytmaning o'zi). Bugungi amallar u yerda shunday ko'rinadi: menyu/fetch/pending → menyu/fetch/fulfilled (payload bilan), taomlar/dishUpdated (Diff tabida faqat price: 35000 → 38000). Kengaytma: amallar ro'yxati, har amaldan keyingi holat, farq (Diff), oldingi holatga sakrash (time travel), amalni qayta yuborish.
Production'da configureStore({ devTools: false }) yoki devTools: import.meta.env.DEV — holat ichini begona ko'zdan yashirish uchun.
7. Kanon tajribasi: server state — TanStack Query'da
Bu darsda vazifalar ga o'zgarish yo'q. Sababi — bugungi uch vositaning ishini kanonda allaqachon boshqa narsa qiladi:
| Vazifa | Redux Toolkit (bugun) | vazifalar kanoni |
|---|---|---|
serverdan yuklash, status |
createAsyncThunk + extraReducers |
TanStack Query useQuery (TanStack Query I) |
| kesh, qayta so'rash, bekor qilish | qo'lda (condition, abort) |
staleTime, cancelQueries — tayyor |
| hosilaviy qiymat | createSelector |
komponentda hisob / select |
| normalizatsiya | createEntityAdapter |
kerak emas: bitta ro'yxat, bitta kalit |
18-qismda vazifalar ning o'qish-yozish kodi aynan thunk uslubidagi qo'lda boshqaruv edi: AbortController pulti, "kutilmoqda" hisobi, tarmoq state'i, har sahifada GET. TanStack Query I–III darslarida u TanStack Query'ga ko'chdi. O'lchov: 7 qadamlik sahifalararo yurishda 4 GET → 2 GET (14-sorovlar-keyin.txt), kod — bitta queryKey atrofida. Thunk bilan xuddi shu natijaga erishish uchun keshni, eskirish vaqtini va takroriy so'rovlarni birlashtirishni o'zingiz yozishingiz kerak bo'lardi.
Redux loyihasida bu ishni Redux'ning o'z vositasi qiladi — RTK Query. Uni keyingi darsda ko'ramiz va kanon tajribasidagi o'lchovi bilan (+24.8 KB) TanStack Query bilan solishtiramiz.
Qachon bugungi vositalar to'g'ri tanlov:
createAsyncThunk— Redux loyihasida bir martalik async jarayon, kesh kerak emas: "buyurtmani yuborish", "faylni eksport qilish".createSelector— Redux bo'lgan joyda doim: har hosilaviy massiv yoki og'ir hisob uchun.createEntityAdapter— ko'p yozuvli, tez-tez bittalab yangilanadigan ro'yxatlar (chat xabarlari, buyurtmalar paneli), ayniqsa bir yozuvga bir nechta joydan havola bo'lsa.- Listener middleware — amaldan kelib chiqadigan yon ta'sirlar: analitika, bildirishnoma, boshqa tablar bilan sinxronlash.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
status abadiy yuklanmoqda |
rejected ushlanmagan; xato bo'lsa holat o'zgarmaydi |
addCase(fetchX.rejected, …) doim |
A non-serializable value… payload da |
payload creator Response yoki Date qaytardi |
await res.json(), sana — satr |
| eski so'rov natijasi yangisini bosib ketdi | ikki so'rov poygasi (race condition) | promise.abort() yoki condition |
createSelector har safar hisoblaydi |
kirish selektorida filter/map yoki selektor komponent ichida yaratilgan |
kirish — faqat olish; selektor — modul darajasida |
Object.keys(entities) — satrlar |
obyekt kalitlari doim satr | tartib va id lar uchun ids |
| amal reducer'ga yetmadi | middleware next(action) ni chaqirmagan |
return next(action); |
extraReducers da addCase xato beradi |
addCase addMatcher/addDefaultCase dan keyin yozilgan |
tartib: addCase → addMatcher → addDefaultCase |
9. Mashqlar
Mashqlar kurs/mashqlar/19/24-redux-async/ da. 2- va 3-mashq Redux Toolkit bilan Node'da (o'tgan darsdagidek: o'z papkangizda @reduxjs/toolkit@2.13.0, .mjs fayl).
1-mashq (oson): uch amal
createAsyncThunk("buyurtma/yubor", …) qaysi uch amal turini yuboradi? Muvaffaqiyatli so'rovda qaysi ikkitasi?
- boshlanishida:
- muvaffaqiyatda:
- xatoda:
Yechim
Muvaffaqiyatli so'rovda — pending va fulfilled; xatoda — pending va rejected. Uchalasi bitta so'rovda hech qachon birga bo'lmaydi. Nom — thunk'ga bergan satringiz + /pending kabi qo'shimcha, shuning uchun uni slice/amal shaklida tanlang.
2-mashq (o'rta): issiq taomlar selektori
createSelector bilan selectHot yozing: faqat hot: true taomlar. Ikki holatda (ikkinchisida faqat stol raqami o'zgargan) natija o'sha massiv bo'lishini va hisob bir marta bajarilganini konsolda ko'rsating.
Yechim
import { createSelector } from "@reduxjs/toolkit";
let runs = 0;
const selectDishes = (state) => state.dishes;
const selectHot = createSelector([selectDishes], (dishes) => {
runs += 1;
return dishes.filter((dish) => dish.hot);
});
const state = {
dishes: [
{ name: "Osh", hot: true },
{ name: "Ko'k choy", hot: false },
{ name: "Manti", hot: true },
],
table: 4,
};
const a = selectHot(state);
const b = selectHot({ ...state, table: 5 });
console.log(a.map((d) => d.name));
console.log("bir xil massiv:", a === b, "| hisob:", runs);Node 24, RTK 2.13.0:
[ 'Osh', 'Manti' ]
bir xil massiv: true | hisob: 1{ ...state, table: 5 } — yangi ildiz obyekti, lekin dishes — o'sha havola. useSelector(selectHot) bunday holatda komponentni chizmaydi va ogohlantirish bermaydi.
3-mashq (qiyin): buyurtmalar paneli
Oshxona paneli uchun:
createEntityAdapterbilanbuyurtmalarslice:orderAdded(adapterningaddOne),orderPaid(id)—paid: true(qoralamani o'zgartirib).selectUnpaidTotal—createSelectorbilan: to'lanmagan buyurtmalar summasi (adapterningselectAlli kirish).- 12-buyurtma (4-stol, 63 000) va 13-buyurtma (7-stol, 35 000) qo'shing, summani chiqaring; 12-ni to'lang, yana chiqaring; oxirida
ids.
Ishora: state.entities[id] — qoralamadagi yozuv, uni to'g'ridan-to'g'ri o'zgartirish mumkin.
Yechim
import {
configureStore,
createEntityAdapter,
createSelector,
createSlice,
} from "@reduxjs/toolkit";
const ordersAdapter = createEntityAdapter();
const ordersSlice = createSlice({
name: "buyurtmalar",
initialState: ordersAdapter.getInitialState(),
reducers: {
orderAdded: ordersAdapter.addOne,
orderPaid: (state, action) => {
const order = state.entities[action.payload];
if (order) order.paid = true;
},
},
});
const { orderAdded, orderPaid } = ordersSlice.actions;
const store = configureStore({
reducer: { buyurtmalar: ordersSlice.reducer },
});
const { selectAll } = ordersAdapter.getSelectors(
(state) => state.buyurtmalar,
);
const selectUnpaidTotal = createSelector([selectAll], (orders) =>
orders
.filter((order) => !order.paid)
.reduce((sum, order) => sum + order.total, 0),
);
store.dispatch(
orderAdded({ id: 12, table: 4, total: 63000, paid: false }),
);
store.dispatch(
orderAdded({ id: 13, table: 7, total: 35000, paid: false }),
);
console.log("to'lanmagan:", selectUnpaidTotal(store.getState()));
store.dispatch(orderPaid(12));
console.log("to'lanmagan:", selectUnpaidTotal(store.getState()));
console.log(store.getState().buyurtmalar.ids);Node 24, RTK 2.13.0:
to'lanmagan: 98000
to'lanmagan: 35000
[ 12, 13 ]sortComparer berilmagan — ids qo'shilish tartibida. orderPaid adapter metodisiz yozildi: Immer bilan entities ichidagi yozuvni o'zgartirish ham to'g'ri. Xuddi shuni ordersAdapter.updateOne(state, { id, changes: { paid: true } }) bilan ham qilsa bo'ladi.
4-mashq (tahlil): thunk yoki Query?
«Bahor» ilovasida uch vazifa bor. Har biri uchun nima tanlaysiz — createAsyncThunk, TanStack Query yoki RTK Query? Loyiha Redux'da emas.
- Menyu ro'yxati: 5 sahifada ko'rinadi, kuniga bir-ikki marta o'zgaradi.
- "Buyurtmani yuborish" tugmasi.
- Oshxona paneli: buyurtmalar har 10 soniyada yangilanadi.
Yechim
Loyiha Redux'da emas — demak Redux faqat shu uchun qo'shilmaydi (o'tgan darsdagi +8.3 KB). Birinchi va uchinchisi — server state: TanStack Query (staleTime, refetchInterval). Ikkinchisi — yozuv: Query'ning useMutation i (TanStack Query III), keyin menyu yoki buyurtmalar keshini yangilash. createAsyncThunk faqat Redux loyihasida, keshlanmaydigan bir martalik ish uchun mantiqli.
10. Real ishda
- Katta Redux kod bazalarida thunk'lar,
createSelectorva entity adapter — kundalik ish. Eski koddaredux-saga(generator funksiyalar bilan) ham uchraydi; yangi kodda — listener middleware yoki RTK Query. - Normalizatsiya — chat, ijtimoiy tarmoq lentasi, admin panellar: bitta foydalanuvchi o'nlab joyda ko'rinadi, ism bir joyda yangilanishi kerak.
- Reselect Redux'siz ham ishlatiladi — har qanday "holatdan hosila" uchun memoizatsiya.
- Intervyu savollari: "reducer'da nega
fetchyozilmaydi?", "createAsyncThunkqaysi amallarni yuboradi?", "extraReducersvareducersfarqi?", "createSelectorqachon qayta hisoblaydi?", "normalizatsiya nima beradi?".
Xulosa
- Reducer sof — async ish thunk'da;
createAsyncThunkpending/fulfilled/rejectedni o'zi yuboradi, slice ularniextraReducersda ushlaydi. - Middleware —
(api) => (next) => (action) => …;nextsiz amal reducer'ga yetmaydi. createSelector— kirishlar o'zgarmasa eski natija (o'sha havola); og'ir ish natija funksiyasida, kirishlar — faqat olish.createEntityAdapter—{ ids, entities }, tayyor CRUD reducer vaselectAll/selectById.- Listener middleware — aniq amalga javob beradigan yon ta'sir, reducer'dan keyin.
vazifalarda server state TanStack Query'da: 4 GET → 2 GET, kesh va bekor qilish tayyor; thunk bilan bularni qo'lda yozish kerak bo'lardi.
Keyingi dars: RTK Query: Redux loyihalarida server state — createApi, avtomatik hooklar, teglar bilan keshni yangilash, auth va sahifalash; TanStack Query bilan o'lchangan taqqoslash.
Manbalar
- Redux Toolkit:
createAsyncThunk,createSlice(extraReducers) — redux-toolkit.js.org/api (v2.13, 2026-10 holatiga) - Redux Toolkit:
createSelector(Reselect 5),createEntityAdapter,createListenerMiddleware— redux-toolkit.js.org/api - Redux: Deriving Data with Selectors; Normalizing State Shape — redux.js.org/usage
- Redux: Writing Custom Middleware — redux.js.org/usage/writing-custom-middleware
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!