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

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 ularni extraReducers da ushlab, status ni yangilaydi. Selektor uchun createSelector hisoblangan qiymatni eslab qoladi: kirishlar o'zgarmasa, eski natija (o'sha havola) qaytadi. createEntityAdapter ro'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

  • createAsyncThunk va extraReducers bilan yuklash holatini (yuklanmoqda, tayyor, xato) boshqarasiz.
  • O'z middleware'ingizni yozib, har amalni jurnalga chiqarasiz.
  • createSelector qachon qayta hisoblashini va qachon eski natijani qaytarishini o'lchaysiz.
  • Ro'yxatni createEntityAdapter bilan normalizatsiya qilasiz.
  • Listener middleware bilan amalga javob beradigan effekt yozasiz.
  • Nega vazifalar da 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.

jsx
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:

text
amal: menyu/pending
amal: menyu/fulfilled

Ekranda:

text
Menyuni yukla
Holat: tayyor
Osh, Manti

Bosilgan 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:

js
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:

text
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 bermadi

Nimalar bo'ldi:

  • createAsyncThunk("menyu/fetch", payloadCreator) — thunk yasovchi. Payload creator — sizning async funksiyangiz: u qaytargan qiymat fulfilled amalining payload iga, tashlagan xatosi — rejected ga tushadi.
  • Amal turlari avtomatik: menyu/fetch/pending, …/fulfilled, …/rejected. Bu nomlar fetchMenu.pending kabi xususiyatlarda ham bor — addCase ularni oladi.
  • extraReducers — slice'ning o'zi yaratmagan amallarga javobi. reducers dagilar slice'ning shaxsiy amallari, extraReducers — tashqaridan (thunk yoki boshqa slice'dan) keladiganlar. O'tgan darsdagi "amal — voqea" g'oyasi: fetchMenu.fulfilled ga 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).
  • status maydoni — Reducer + Immer va holat mashinasi darsidagi "boolean'lar emas, bitta status" qoidasi: yuklanmoqda va xato bir 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 / xato

2.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. createAsyncThunk payload creator'ning ikkinchi argumentida signal (AbortController) beradi, dispatch(fetchMenu()) qaytargan promise'da esa .abort() metodi. Komponent yopilganda effekt tozalash funksiyasida promise.abort() — eski so'rov natijasi kech kelib holatni buzmaydi.

Tekshirib ko'ring: Payload creator return fetch(url) deb, javobni .json() siz qaytarsa, fulfilled ning payload ida 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):

js
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); // 1

s2 — 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):

js
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:

text
35000 hisoblandi: 1
35000 hisoblandi: 1
35000 hisoblandi: 1
40000 hisoblandi: 2

Ofitsiant 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:

js
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); // Manti

entities[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:

js
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:

text
{
  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 }
2
  • getInitialState() — { 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'ri reducers ga qo'yiladi yoki boshqa reducer ichida chaqiriladi: dishesAdapter.addMany(state, action.payload).
  • sortComparer — ids tartibi: nom bo'yicha ("Lag'mon", "Manti", "Osh"). entities dagi kalitlar tartibi muhim emas: obyekt kalitlari son bo'lsa, JavaScript ularni o'sish tartibida chiqaradi.
  • getSelectors — selectAll, selectById, selectIds, selectTotal. selectAll memoizatsiyalangan: tartiblangan massiv faqat ids yoki entities o'zgarganda qayta yasaladi.

Diqqat: entities dagi kalitlar — satr ('7'), garchi id son bo'lsa ham. JavaScript obyekt kalitlari doim satr. entities[7] ham ishlaydi (son satrga aylanadi), lekin Object.keys(entities) — ['3', '7', '9'], sonlar emas. id sifatida doim ids massividan 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".

js
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:

text
effekt: Osh, jami 35000
effekt: Manti, jami 65000
Katta buyurtma — oshpazga xabar

Effekt 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 added reducerining ichida state.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
js
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:

text
[ '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:

  1. createEntityAdapter bilan buyurtmalar slice: orderAdded (adapterning addOne), orderPaid(id) — paid: true (qoralamani o'zgartirib).
  2. selectUnpaidTotal — createSelector bilan: to'lanmagan buyurtmalar summasi (adapterning selectAll i kirish).
  3. 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
js
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:

text
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.

  1. Menyu ro'yxati: 5 sahifada ko'rinadi, kuniga bir-ikki marta o'zgaradi.
  2. "Buyurtmani yuborish" tugmasi.
  3. 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, createSelector va entity adapter — kundalik ish. Eski kodda redux-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 fetch yozilmaydi?", "createAsyncThunk qaysi amallarni yuboradi?", "extraReducers va reducers farqi?", "createSelector qachon qayta hisoblaydi?", "normalizatsiya nima beradi?".

Xulosa

  • Reducer sof — async ish thunk'da; createAsyncThunk pending/fulfilled/rejected ni o'zi yuboradi, slice ularni extraReducers da ushlaydi.
  • Middleware — (api) => (next) => (action) => …; next siz 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 va selectAll/selectById.
  • Listener middleware — aniq amalga javob beradigan yon ta'sir, reducer'dan keyin.
  • vazifalar da 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
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Redux Toolkit async: createAsyncThunk, createSelector, createEntityAdapter va listener — IlmHamroh