Redux Toolkit — createAsyncThunk, RTK Query
Muammodan boshlaylik
60-darsda reducer lar sof funksiya bo'lishi kerakligini aytdik: ichida fetch yo'q. Lekin ilovaga serverdan ma'lumot kerak. Qayerga yozamiz?
// ❌ reducer ichida — mumkin emas
reducers: {
yukla: async (state) => {
const j = await fetch('/api/mahsulotlar'); // reducer async bo'la olmaydi
state.mahsulotlar = await j.json();
},
}
Redux Toolkit da async mantiq uchun ikki vosita bor:
createAsyncThunk— reducer dan tashqarida async funksiya; natijasini action lar orqali reducer ga beradi- RTK Query — server ma'lumotlari uchun to'liq yechim (TanStack Query ga o'xshash)
Thunk nima
Thunk — action o'rniga dispatch qilinadigan funksiya. configureStore ga o'rnatilgan thunk middleware uni ushlaydi va chaqiradi:
// Oddiy action — obyekt
dispatch({ type: 'savat/tozalash' });
// Thunk — funksiya
dispatch(async (dispatch, getState) => {
const j = await fetch('/api/mahsulotlar');
const data = await j.json();
dispatch(mahsulotlarKeldi(data)); // natijani oddiy action bilan yuborish
});
Thunk ichida: async kod, dispatch (bir yoki bir necha marta), getState(). Reducer lar esa sof qoladi.
createAsyncThunk
Har bir so'rovda uch holat bor: yuklanmoqda, muvaffaqiyat, xato (49-dars). createAsyncThunk ular uchun action larni avtomatik yaratadi:
// features/mahsulotlar/mahsulotlarSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const mahsulotlarniYukla = createAsyncThunk(
'mahsulotlar/yukla', // action type prefiksi
async (kategoriya, { signal, rejectWithValue }) => {
const j = await fetch(`/api/mahsulotlar?kategoriya=${kategoriya}`, { signal });
if (!j.ok) return rejectWithValue(`Server xatosi: ${j.status}`);
return j.json(); // → action.payload
}
);
Bitta createAsyncThunk uchta action type yaratadi:
| Action | Qachon | payload |
|---|---|---|
mahsulotlar/yukla/pending |
So'rov boshlandi | — |
mahsulotlar/yukla/fulfilled |
Muvaffaqiyat | return qilingan qiymat |
mahsulotlar/yukla/rejected |
Xato | rejectWithValue(...) qiymati; action.error da xato |
extraReducers — thunk natijalarini ushlash
const mahsulotlarSlice = createSlice({
name: 'mahsulotlar',
initialState: {
royxat: [],
holat: 'idle', // 'idle' | 'yuklanmoqda' | 'muvaffaqiyat' | 'xato'
xato: null,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(mahsulotlarniYukla.pending, (state) => {
state.holat = 'yuklanmoqda';
state.xato = null;
})
.addCase(mahsulotlarniYukla.fulfilled, (state, action) => {
state.holat = 'muvaffaqiyat';
state.royxat = action.payload;
})
.addCase(mahsulotlarniYukla.rejected, (state, action) => {
if (action.meta.aborted) return; // bekor qilingan — xato emas
state.holat = 'xato';
state.xato = action.payload ?? action.error.message;
});
},
});
reducers— slice ning o'z action lariextraReducers— tashqaridan keladigan action lar (thunk lar, boshqa slice lar)
Komponentda
function Mahsulotlar({ kategoriya }) {
const dispatch = useDispatch();
const { royxat, holat, xato } = useSelector((s) => s.mahsulotlar);
useEffect(() => {
const promise = dispatch(mahsulotlarniYukla(kategoriya));
return () => promise.abort(); // race condition dan himoya (50-dars)
}, [kategoriya, dispatch]);
if (holat === 'yuklanmoqda') return <Skeleton />;
if (holat === 'xato') return <p role="alert">{xato}</p>;
return <ul>{royxat.map((m) => <li key={m.id}>{m.nomi}</li>)}</ul>;
}
dispatch(thunk) qaytargan promise ning .abort() metodi bor — u signal orqali fetch ni to'xtatadi.
[!INFO]
useSelector((s) => s.mahsulotlar)bu yerda xavfsiz: u store dagi mavjud obyektni qaytaradi, yangisini yaratmaydi. Obyekt faqat slice o'zgarganda yangilanadi.
Natijani komponentda kutish — unwrap
async function saqla() {
try {
const yangi = await dispatch(mahsulotQosh(forma)).unwrap();
navigate(`/mahsulot/${yangi.id}`);
} catch (xato) {
toast.error(xato);
}
}
.unwrap() — fulfilled da payload ni qaytaradi, rejected da throw qiladi.
Muammo: bu ko'p kod
Bitta ro'yxatni yuklash uchun: thunk, uchta holat, uchta addCase, useEffect, abort. Yana kerak bo'ladi: kesh, deduplikatsiya, fokusda yangilash, invalidatsiya... Bu 51-darsdagi muammoning aynan o'zi.
Shuning uchun Redux jamoasi RTK Query ni yaratdi.
RTK Query
Server ma'lumotlari uchun — TanStack Query ning Redux ichidagi muqobili:
// services/api.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['Mahsulot'],
endpoints: (builder) => ({
getMahsulotlar: builder.query({
query: (kategoriya) => `mahsulotlar?kategoriya=${kategoriya}`,
providesTags: ['Mahsulot'],
}),
getMahsulot: builder.query({
query: (id) => `mahsulotlar/${id}`,
providesTags: (natija, xato, id) => [{ type: 'Mahsulot', id }],
}),
addMahsulot: builder.mutation({
query: (yangi) => ({ url: 'mahsulotlar', method: 'POST', body: yangi }),
invalidatesTags: ['Mahsulot'],
}),
}),
});
export const {
useGetMahsulotlarQuery,
useGetMahsulotQuery,
useAddMahsulotMutation,
} = api;
Har bir endpoint uchun hook avtomatik yaratiladi: use + nom + Query/Mutation.
Store ga ulash
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import { api } from '../services/api';
export const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer,
savat: savatReducer,
},
middleware: (getDefault) => getDefault().concat(api.middleware),
});
Komponentda
function Mahsulotlar({ kategoriya }) {
const { data, isLoading, isFetching, error } = useGetMahsulotlarQuery(kategoriya);
if (isLoading) return <Skeleton />;
if (error) return <p role="alert">Xato yuz berdi</p>;
return <ul>{data.map((m) => <li key={m.id}>{m.nomi}</li>)}</ul>;
}
function YangiMahsulot() {
const [qosh, { isLoading }] = useAddMahsulotMutation();
return (
<button disabled={isLoading} onClick={() => qosh({ nomi: 'Kitob', narx: 50000 })}>
Qo'shish
</button>
);
}
thunk, extraReducers, useEffect — hech biri yo'q. Kesh, deduplikatsiya, race condition — avtomatik.
Teglar — invalidatsiya
providesTags: ['Mahsulot']— "bu so'rovMahsulotma'lumotini beradi"invalidatesTags: ['Mahsulot']— "bu mutatsiyaMahsulotni eskirtiradi"
Mahsulot qo'shilganda Mahsulot tegli barcha so'rovlar avtomatik qayta yuklanadi — TanStack Query dagi invalidateQueries (53-dars) ning deklarativ ko'rinishi.
Qaysi birini tanlash
| Vaziyat | Tanlov |
|---|---|
| Server ma'lumotlarini olish va o'zgartirish | RTK Query (yoki TanStack Query) |
| Bir nechta slice ga ta'sir qiluvchi murakkab async jarayon (masalan, login → profil yuklash → sozlamalarni qo'llash) | createAsyncThunk |
| Loyihada Redux yo'q | TanStack Query (51–55-darslar) |
| Loyihada Redux bor | RTK Query — qo'shimcha kutubxonasiz |
[!TIP] RTK Query va TanStack Query — bir xil muammoni hal qiladi. Bitta loyihada ikkalasini ishlatmang. Agar Redux allaqachon bor bo'lsa — RTK Query; Redux yo'q bo'lsa, faqat server ma'lumotlari uchun Redux qo'shishning ma'nosi yo'q — TanStack Query.
Nima uchun shunday?
Nima uchun Redux reducer larida async kod taqiqlangan — bu shunchaki noqulay cheklov emasmi?
Bu Redux ning eng muhim kafolati: bir xil state + bir xil action = har doim bir xil natija. Shu kafolat tufayli DevTools da "vaqt bo'ylab sayohat" mumkin — eski action larni qayta "o'ynatish" har doim aynan o'sha state ni beradi. Agar reducer ichida fetch bo'lsa, har bir qayta o'ynatish yangi so'rov yuboradi va boshqa natija oladi — tarix ma'nosiz bo'ladi. Thunk lar bu muammoni chiroyli ajratadi: "dunyo bilan aloqa" (tarmoq, vaqt, tasodifiylik) thunk da, "state qanday o'zgaradi" esa sof reducer da. Natijada tarixdagi har bir action — server javobi bilan birga — yozib qo'yilgan va qayta ishlab chiqarish mumkin bo'lgan haqiqat.
Xulosa
Eslab qoling:
- Reducer — sof; async kod thunk da
createAsyncThunk(type, async (arg, { signal, rejectWithValue }) => ...)→pending,fulfilled,rejected- Natijalar
extraReducersdabuilder.addCase(...)bilan dispatch(thunk(arg))→.abort()va.unwrap()- RTK Query:
createApi+fetchBaseQuery+endpoints→ avtomatik hook lar providesTags/invalidatesTags— avtomatik qayta yuklash- Server ma'lumoti — RTK Query yoki TanStack Query (ikkalasi emas); murakkab jarayon — thunk
Keyingi bo'lim
9-bo'lim: Formalar — HTML forma, React Hook Form va Zod.