Mundarija (25)
- Bu darsda
- 1. Nega bu kerak?
- 2. Birinchi API
- 2.1 createApi va fetchBaseQuery
- 2.2 Avtomatik hooklar
- 2.3 Xuddi shu g'oya — jonli, TanStack Query bilan
- 3. Teglar chuqurroq
- 3.1 id li teglar
- 3.2 Optimistik yangilash: onQueryStarted
- 4. Auth: token va 401
- 4.1 prepareHeaders
- 4.2 401 da yangilash: baseQuery ni o'rash
- 5. Sahifalash va codegen
- 5.1 Har sahifa — alohida kesh yozuvi
- 5.2 Codegen: endpoint'larni OpenAPI'dan yasash
- 6. Kanon tajribasi: to'rt yo'l, bir vazifa (nega kanonga kirmadi)
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): teglarni qo'ying
- 2-mashq (o'rta): faqat o'zgargan taom
- 3-mashq (qiyin): optimistik savat
- 4-mashq (tahlil): qaysi biri?
- 9. Real ishda
- Xulosa
- Manbalar
RTK Query: createApi, teglar bilan kesh yangilash va Redux loyihalarida server state
Qisqacha: RTK Query — Redux Toolkit ichidagi server state vositasi: TanStack Query'ning Redux'dagi "egizagi".
createApidabaseQuery(odatdafetchBaseQuery) vaendpointsyoziladi —build.queryo'qish,build.mutationyozish uchun. Har endpoint uchun hook avtomatik yasaladi:useGetMenuQuery,useAddDishMutation. Kesh teglar bilan yangilanadi: so'rovprovidesTags: ["Menyu"]deydi, mutatsiyainvalidatesTags: ["Menyu"]— shu tegli so'rovlar o'zi qayta yuklanadi. Kanon tajribasida (qo'lda yozilgan variantga nisbatan) RTK Query +24.8 KB, TanStack Query +10.3 KB: Redux loyihasida — RTK Query, Redux'siz loyihada — TanStack Query.
Bu darsda
createApi,fetchBaseQueryva endpoint'lar bilan API qatlami yozasiz.- Avtomatik hooklar (
useGetMenuQuery) qanday nomlanishini va nima qaytarishini bilasiz. providesTags/invalidatesTagsbilan keshni yangilaysiz — umumiy teg vaidli teg.- Token qo'shadigan va 401 da yangilaydigan
baseQueryyozasiz. - Sahifalash va kod generatsiyasini (codegen) taniysiz.
- RTK Query va TanStack Query'ni o'lchov bilan solishtirib, qaysi loyihada qaysi biri ekanini ayta olasiz.
Oldin bilishingiz kerak: Redux Toolkit II, TanStack Query II: kesh hayot sikli va kalitlar, TanStack Query III: mutatsiyalar, Server state vs client state.
1. Nega bu kerak?
O'tgan darsda menyuni createAsyncThunk bilan yukladik. Ishladi, lekin oxirida ro'yxat chiqqan edi: kesh, takroriy so'rovlarni birlashtirish, eskirish vaqti, bekor qilish — hammasini qo'lda yozish kerak. TanStack Query bularni tayyor beradi va vazifalar da aynan u ishlaydi.
Endi Sardorning bank loyihasini eslang: hammasi Redux'da. TanStack Query qo'shilsa, ilovada ikki "kesh" bo'ladi: biri Redux store'da, biri Query mijozida. DevTools ikkita, qoidalar ikkita. Redux jamoasi bu muammoga o'z javobini bergan — RTK Query. U Redux Toolkit paketining ichida, kesh — oddiy Redux holati, so'rovlar — oddiy Redux amallari.
Hayotiy o'xshatish: kutubxona kartotekasi. Kitob olgan har bir o'quvchi kartochkada belgilanadi (providesTags). Kitobning yangi nashri kelsa (invalidatesTags), kutubxonachi kartochkaga qaraydi va faqat shu kitobni olganlarga "yangisini olib keting" deydi. Hammaga emas.
2. Birinchi API
2.1 createApi va fetchBaseQuery
RTK Query alohida o'rnatilmaydi: @reduxjs/toolkit 2.13.0 ichida (@reduxjs/toolkit/query, React hooklari bilan — @reduxjs/toolkit/query/react). Saytdagi natija oynasida u yo'q. Shuning uchun avval Node'da, tarmoq o'rniga soxta fetch bilan ishga tushiramiz — so'rovlarni ko'rish uchun eng qulay yo'l:
import { configureStore } from "@reduxjs/toolkit";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query";
// soxta server: tarmoq o'rniga (Node'da)
const dishes = [{ id: 1, name: "Osh" }];
globalThis.fetch = async (request) => {
const url = new URL(request.url);
console.log("so'rov:", request.method, url.pathname);
if (request.method === "POST") {
const body = await request.json();
dishes.push({ id: dishes.length + 1, name: body.name });
return Response.json(dishes.at(-1), { status: 201 });
}
return Response.json(dishes);
};
const menuApi = createApi({
reducerPath: "menuApi",
baseQuery: fetchBaseQuery({ baseUrl: "https://example.com/api/" }),
tagTypes: ["Menyu"],
endpoints: (build) => ({
getMenu: build.query({
query: () => "menyu",
providesTags: ["Menyu"],
}),
addDish: build.mutation({
query: (name) => ({
url: "menyu",
method: "POST",
body: { name },
}),
invalidatesTags: ["Menyu"],
}),
}),
});
const store = configureStore({
reducer: { [menuApi.reducerPath]: menuApi.reducer },
middleware: (getDefault) =>
getDefault().concat(menuApi.middleware),
});
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
const select = menuApi.endpoints.getMenu.select();
const sub = store.dispatch(menuApi.endpoints.getMenu.initiate());
await sub;
console.log("kesh:", select(store.getState()).data);
// ikkinchi komponent ham so'radi — yangi so'rov yo'q
const sub2 = store.dispatch(menuApi.endpoints.getMenu.initiate());
await sub2;
await store.dispatch(menuApi.endpoints.addDish.initiate("Manti"));
await wait(50);
console.log("kesh:", select(store.getState()).data);
sub.unsubscribe();
sub2.unsubscribe();Toza papkada @reduxjs/toolkit@2.13.0 bilan Node 24 da:
so'rov: GET /api/menyu
kesh: [ { id: 1, name: 'Osh' } ]
so'rov: POST /api/menyu
so'rov: GET /api/menyu
kesh: [ { id: 1, name: 'Osh' }, { id: 2, name: 'Manti' } ]Qadamma-qadam:
| Qadam | Server | Kesh (menuApi) |
|---|---|---|
sub — getMenu |
GET | [Osh] |
sub2 — yana getMenu |
— | [Osh] (keshdan) |
addDish("Manti") |
POST | "Menyu" tegi eskirdi |
| teg bo'yicha qayta yuklash | GET | [Osh, Manti] |
Uchta muhim narsa:
- Bitta GET.
getMenuikki marta so'raldi (subvasub2— ikki komponent kabi), lekin server bir marta chaqirildi. Ikkinchisi keshdan oldi. TanStack Query'dagi bir xilqueryKeybilan so'rovlar birlashishi (TanStack Query II) — bu yerda ham. - Teg ishladi.
addDishmuvaffaqiyatli tugadi → uninginvalidatesTags: ["Menyu"]i →providesTags: ["Menyu"]ligetMenuo'zi qayta yuklandi (oxirgi GET). Keshda endi manti ham bor. Bizrefetchni hech qayerda chaqirmadik. - Hammasi Redux.
menuApi.reducer— store'dagi oddiy slice (menuApikalitida),menuApi.middleware— keshning hayot siklini boshqaradi (obunalar, taymerlar, teglar). Ikkalasi ham ulanishi shart.
Qismlarga to'xtalamiz:
baseQuery— har so'rov o'tadigan umumiy funksiya.fetchBaseQuery—fetchustidagi yupqa o'ram:baseUrl, JSON'ni o'zi o'qiydi,!res.okbo'lsa xato qaytaradi (tashlamaydi). API qatlami darsidagiapi.tsning vazifasi.endpoints— API'ning "menyusi".build.query— o'qish,build.mutation— yozish.queryfunksiyasi so'rovni tasvirlaydi: satr ("menyu"—baseUrlga qo'shiladi) yoki{ url, method, body }obyekti.body— obyekt bo'lsa, JSON'ga o'zi aylanadi.tagTypes— ishlatiladigan teg nomlari ro'yxati.
2.2 Avtomatik hooklar
React loyihasida import @reduxjs/toolkit/query/react dan bo'ladi va createApi har endpoint uchun hook yasaydi. Nomi qoida bo'yicha: use + endpoint nomi (bosh harf bilan) + Query/Mutation. Kanon tajribasidagi variant (aynan):
// server-rtkq.tsx — RTK Query (Redux Toolkit 2): createApi, teglar bilan
// invalidatsiya. Redux store va Provider majburiy
import { configureStore } from "@reduxjs/toolkit";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { Provider } from "react-redux";
import { ishgaTushir } from "./_umumiy.tsx";
import { Korinish, URL_ } from "./_server.tsx";
import type { Royxat, Vazifa } from "./_server.tsx";
const vazifalarApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: URL_ }),
tagTypes: ["Vazifalar"],
endpoints: (b) => ({
royxat: b.query<Royxat, void>({ query: () => "", providesTags: ["Vazifalar"] }),
qosh: b.mutation<Vazifa, string>({ query: (matn) => ({ url: "", method: "POST", body: { matn } }), invalidatesTags: ["Vazifalar"] }),
}),
});
const store = configureStore({
reducer: { [vazifalarApi.reducerPath]: vazifalarApi.reducer },
middleware: (m) => m().concat(vazifalarApi.middleware),
});
function Ilova() {
const { data, isLoading, error } = vazifalarApi.useRoyxatQuery();
const [qosh] = vazifalarApi.useQoshMutation();
return (
<Korinish
vazifalar={data?.vazifalar}
yuklanmoqda={isLoading}
xato={error ? JSON.stringify(error) : null}
onQosh={(matn) => void qosh(matn)}
/>
);
}
ishgaTushir(
<Provider store={store}>
<Ilova />
</Provider>,
);vazifalarApi.useRoyxatQuery()—royxatendpoint'i uchun. Qaytaradi:data,isLoading(birinchi yuklash),isFetching(har yuklash),error,refetch. TanStack Query'dagiuseQuerynatijasiga juda o'xshash.vazifalarApi.useQoshMutation()—[funksiya, holat]juftligi:const [qosh, { isLoading }] = ….qosh(matn)promise qaytaradi;.unwrap()bilan natija yoki xatoni oddiy promise kabi olasiz.Provider— oddiy react-redux Provider'i (Redux Toolkit I). Redux loyihasida u allaqachon bor.
Bu variant «Bahor» mashq API'si bilan Chrome 155 da ishlatildi (natijalar/tajriba/16-17-server-ishlaydi.txt):
Chrome: Chrome/155.0.8059.40 | variant: server-rtkq
ro'yxat: ["Tajriba vazifa"]
so'rovlar: GET tajriba-dgcvgb4crev, POST tajriba-dgcvgb4crev, OPTIONS tajriba-dgcvgb4crev, GET tajriba-dgcvgb4crevGET → POST → GET: yuklash, qo'shish, teg bo'yicha qayta yuklash. O'rtadagi OPTIONS — brauzerning CORS preflight so'rovi (POST JSON boshqa domenga ketdi), RTK Query'ga aloqasi yo'q. TanStack Query, SWR va qo'lda yozilgan variantlarda ham aynan shu to'rt so'rov.
2.3 Xuddi shu g'oya — jonli, TanStack Query bilan
RTK Query natija oynasida yo'q, lekin uning g'oyasi — "yozuvdan keyin tegishli so'rov o'zi qayta yuklansin" — TanStack Query'da ham bor va u saytda ishlaydi. Teg o'rnida — queryKey, invalidatesTags o'rnida — invalidateQueries:
import {
QueryClient,
QueryClientProvider,
useMutation,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
// soxta server (natija oynasida tarmoq yo'q)
const dishes = ["Osh"];
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
async function getMenu() {
console.log("GET /menyu");
await delay(200);
return [...dishes];
}
async function addDish(name) {
console.log("POST /menyu");
await delay(200);
dishes.push(name);
}
function Menu() {
const client = useQueryClient();
const { data = [] } = useQuery({
queryKey: ["menyu"],
queryFn: getMenu,
});
const add = useMutation({
mutationFn: addDish,
onSuccess: () =>
client.invalidateQueries({ queryKey: ["menyu"] }),
});
return (
<>
<p>Menyu: {data.join(", ")}</p>
<button onClick={() => add.mutate("Manti")}>Manti qo'sh</button>
</>
);
}
const client = new QueryClient();
export default function App() {
return (
<QueryClientProvider client={client}>
<Menu />
</QueryClientProvider>
);
}Konsolda:
GET /menyu
POST /menyu
GET /menyuEkranda:
Menyu: Osh, Manti
Manti qo'shKetma-ketlik Node'dagi RTK Query bilan bir xil. Farq — kim bog'laydi: TanStack Query'da mutatsiya o'zi aytadi "qaysi kalitlarni yangila" (onSuccess ichida). RTK Query'da esa ikkala tomon e'lon qiladi: so'rov "men nimani beraman", mutatsiya "men nimani eskirtiraman" — bog'lanishni kutubxona topadi.
| TanStack Query | RTK Query | |
|---|---|---|
| so'rov kaliti | queryKey: ["menyu"] |
endpoint + argument: getPage(1) |
| yozuvdan keyin yangilash | invalidateQueries({ queryKey }) |
invalidatesTags ↔ providesTags |
| kesh qayerda | QueryClient |
Redux store (menuApi slice) |
| hooklar | useQuery, useMutation (umumiy) |
useGetMenuQuery (har endpoint uchun) |
Tekshirib ko'ring:
addDishendpoint'idainvalidatesTagsyozilmasa, Node misolidagi so'rovlar ro'yxati qanday bo'ladi?
Javob
GET, keyin POST — va boshqa hech narsa. Mutatsiya serverda manti qo'shadi, lekin keshdagi ro'yxat eski qoladi: oxirgi kesh: qatorida faqat osh. Ro'yxat qayta yuklanishi uchun nimadir uni eskirtirishi kerak — teg, refetch() yoki keepUnusedDataFor muddati tugashi va qayta obuna.
3. Teglar chuqurroq
3.1 id li teglar
["Menyu"] — umumiy teg: har qanday o'zgarish hamma menyu so'rovlarini qayta yuklaydi. 300 taomli menyuda bitta narx o'zgarsa, 300 ta taom sahifasi qayta so'ralishi kerak emas. Teg aniqroq bo'lishi mumkin — { type, id }:
import { configureStore } from "@reduxjs/toolkit";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query";
const db = {
1: { id: 1, name: "Osh", price: 35000 },
2: { id: 2, name: "Manti", price: 30000 },
};
globalThis.fetch = async (request) => {
const id = new URL(request.url).pathname.split("/").at(-1);
console.log("so'rov:", request.method, id);
if (request.method === "PATCH") {
Object.assign(db[id], await request.json());
}
return Response.json(db[id]);
};
const menuApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: "https://example.com/api/" }),
tagTypes: ["Taom"],
endpoints: (build) => ({
getDish: build.query({
query: (id) => `taom/${id}`,
providesTags: (result, error, id) => [{ type: "Taom", id }],
}),
setPrice: build.mutation({
query: ({ id, price }) => ({
url: `taom/${id}`,
method: "PATCH",
body: { price },
}),
invalidatesTags: (result, error, { id }) => [
{ type: "Taom", id },
],
}),
}),
});
const store = configureStore({
reducer: { [menuApi.reducerPath]: menuApi.reducer },
middleware: (getDefault) =>
getDefault().concat(menuApi.middleware),
});
const { getDish, setPrice } = menuApi.endpoints;
store.dispatch(getDish.initiate(1));
store.dispatch(getDish.initiate(2));
await new Promise((r) => setTimeout(r, 50));
console.log("--- 1-taom narxi o'zgaradi");
await store.dispatch(setPrice.initiate({ id: 1, price: 38000 }));
await new Promise((r) => setTimeout(r, 50));
console.log(getDish.select(1)(store.getState()).data.price);Node 24, RTK 2.13.0:
so'rov: GET 1
so'rov: GET 2
--- 1-taom narxi o'zgaradi
so'rov: PATCH 1
so'rov: GET 1
38000providesTags endi funksiya: (natija, xato, argument) => teglar. Har taom so'rovi o'z id sini beradi: { type: "Taom", id: 1 }. Mutatsiya faqat { type: "Taom", id: 1 } ni eskirtirdi — 2-taom qayta so'ralmadi (PATCH dan keyin faqat GET 1).
Ro'yxat va bitta yozuvni birga ishlatish uchun odatiy naqsh:
getMenu: build.query({
query: () => "menyu",
providesTags: (result = []) => [
...result.map(({ id }) => ({ type: "Taom", id })),
{ type: "Taom", id: "RO'YXAT" },
],
}),
addDish: build.mutation({
query: (body) => ({ url: "menyu", method: "POST", body }),
// yangi taom — ro'yxat o'zgardi, mavjud taomlar emas
invalidatesTags: [{ type: "Taom", id: "RO'YXAT" }],
}),"RO'YXAT" — maxsus id: "butun ro'yxat" degan teg. Qo'shish ro'yxatni, tahrir — bitta taomni (va u ro'yxatda ham bor — ro'yxat ham yangilanadi, chunki unda shu id li teg bor).
3.2 Optimistik yangilash: onQueryStarted
Teglar "server javobidan keyin qayta yukla" deydi. Ba'zan javobni kutmasdan ekranni darhol yangilash kerak — useOptimistic darsidagi g'oya. RTK Query'da: mutatsiyaning onQueryStarted ida keshni qo'lda tuzatish (updateQueryData — Immer qoralamasi bilan) va xato bo'lsa — patch.undo(). Buni 3-mashqda yozasiz.
4. Auth: token va 401
4.1 prepareHeaders
Himoyalangan API har so'rovda token kutadi. fetchBaseQuery ning prepareHeaders i har so'rovdan oldin chaqiriladi va sarlavhalarni to'ldiradi. Token Redux holatida bo'lsa — getState() bilan olinadi.
Diqqat: bu misoldagi token — o'quv uchun soxta satr. Token qayerda saqlanishi (xotira,
httpOnlycookie), qanday yangilanishi va serverda tekshirilishi — Autentifikatsiyali so'rovlar va 28-qismdagi haqiqiy auth mavzusi.localStoragedagi token XSS bilan o'g'irlanishi mumkin.
4.2 401 da yangilash: baseQuery ni o'rash
Token eskirsa, server 401 qaytaradi. Har endpoint'da buni tekshirmaslik uchun baseQuery ning o'zini o'raymiz: xato 401 bo'lsa — yangi token so'raymiz va asl so'rovni qaytaramiz:
import { configureStore, createSlice } from "@reduxjs/toolkit";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query";
// soxta server: "eski-token" muddati o'tgan, "yangi-token" ishlaydi
globalThis.fetch = async (request) => {
const url = new URL(request.url);
const auth = request.headers.get("authorization");
console.log("so'rov:", url.pathname, auth);
if (url.pathname === "/api/yangila") {
return Response.json({ token: "yangi-token" });
}
if (auth !== "Bearer yangi-token") {
return Response.json({ xato: "token eskirgan" }, { status: 401 });
}
return Response.json([{ id: 12, total: 63000 }]);
};
const authSlice = createSlice({
name: "auth",
initialState: { token: "eski-token" },
reducers: {
tokenReceived(state, action) {
state.token = action.payload;
},
},
});
const rawBaseQuery = fetchBaseQuery({
baseUrl: "https://example.com/api/",
prepareHeaders: (headers, { getState }) => {
const { token } = getState().auth;
if (token) headers.set("authorization", `Bearer ${token}`);
return headers;
},
});
const baseQueryWithReauth = async (args, api, extra) => {
let result = await rawBaseQuery(args, api, extra);
if (result.error?.status === 401) {
const refresh = await rawBaseQuery("yangila", api, extra);
if (refresh.data) {
const { tokenReceived } = authSlice.actions;
api.dispatch(tokenReceived(refresh.data.token));
result = await rawBaseQuery(args, api, extra);
}
}
return result;
};
const ordersApi = createApi({
baseQuery: baseQueryWithReauth,
endpoints: (build) => ({
getOrders: build.query({ query: () => "buyurtmalar" }),
}),
});
const store = configureStore({
reducer: {
auth: authSlice.reducer,
[ordersApi.reducerPath]: ordersApi.reducer,
},
middleware: (getDefault) =>
getDefault().concat(ordersApi.middleware),
});
const result = await store.dispatch(
ordersApi.endpoints.getOrders.initiate(),
);
console.log("natija:", result.data);
console.log("token:", store.getState().auth.token);Node 24, RTK 2.13.0:
so'rov: /api/buyurtmalar Bearer eski-token
so'rov: /api/yangila Bearer eski-token
so'rov: /api/buyurtmalar Bearer yangi-token
natija: [ { id: 12, total: 63000 } ]
token: yangi-tokenBirinchi so'rov eski token bilan — 401. O'ram buni ko'rib, /yangila ga murojaat qildi, yangi tokenni Redux'ga yozdi (tokenReceived) va asl so'rovni qayta yubordi — endi prepareHeaders yangi tokenni qo'ydi. Komponent bularning hech birini bilmaydi: u faqat natijani oldi.
Token yangilash navbati darsidagi muammo bu yerda ham bor: bir vaqtda o'nta so'rov 401 olsa, o'nta yangilash ketmasin. Shuning uchun haqiqiy loyihada o'ram ichida "yangilash ketyapti" belgisini (masalan, async-mutex kutubxonasi bilan qulf) saqlashadi.
5. Sahifalash va codegen
5.1 Har sahifa — alohida kesh yozuvi
RTK Query'da so'rov kaliti (hujjatda — cache key) — endpoint nomi va argument. Sahifa raqamini argument qilsangiz, har sahifa o'z yozuviga tushadi:
import { configureStore } from "@reduxjs/toolkit";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query";
const all = ["Osh", "Lag'mon", "Manti", "Ko'k choy"];
globalThis.fetch = async (request) => {
const url = new URL(request.url);
console.log("so'rov:", url.search);
const page = Number(url.searchParams.get("sahifa"));
return Response.json(all.slice((page - 1) * 2, page * 2));
};
const menuApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: "https://example.com/api/" }),
endpoints: (build) => ({
getPage: build.query({
query: (page) => `menyu?sahifa=${page}&hajm=2`,
}),
}),
});
const store = configureStore({
reducer: { [menuApi.reducerPath]: menuApi.reducer },
middleware: (getDefault) =>
getDefault().concat(menuApi.middleware),
});
const { getPage } = menuApi.endpoints;
console.log((await store.dispatch(getPage.initiate(1))).data);
console.log((await store.dispatch(getPage.initiate(2))).data);
console.log((await store.dispatch(getPage.initiate(1))).data);
console.log(Object.keys(store.getState().api.queries));Node 24, RTK 2.13.0:
so'rov: ?sahifa=1&hajm=2
[ 'Osh', "Lag'mon" ]
so'rov: ?sahifa=2&hajm=2
[ 'Manti', "Ko'k choy" ]
[ 'Osh', "Lag'mon" ]
[ 'getPage(1)', 'getPage(2)' ]Uchinchi chaqiruv — 1-sahifa yana: so'rov yo'q, keshdan. Oxirgi qatorda kesh kalitlari: getPage(1), getPage(2) — argument kalitning bir qismi. React'da: useGetPageQuery(page) — page state o'zgarsa, yangi sahifa yuklanadi, eskisiga qaytsangiz — darhol. Yangi sahifa yuklanayotganda eskisini ko'rsatib turish uchun isFetching (yuklanmoqda) va data (oxirgi muvaffaqiyatli natija) ni birga ishlatasiz.
"Yana yuklash" (cheksiz ro'yxat) uchun Redux Toolkit 2.6 dan beri alohida build.infiniteQuery bor — TanStack Query'dagi useInfiniteQuery (TanStack Query IV) ning analogi: getNextPageParam va sahifalar massivi.
5.2 Codegen: endpoint'larni OpenAPI'dan yasash
Katta API'da 200 ta endpoint bo'lsa, ularni qo'lda yozish — xato manbai. Agar backend OpenAPI (Swagger) hujjati bersa — har endpoint, parametr va javob turi tasvirlangan JSON fayl — RTK Query uchun kodni generator yasaydi:
npx @rtk-query/codegen-openapi openapi-config.ts// openapi-config.ts — generator sozlamasi (namuna)
import type { ConfigFile } from "@rtk-query/codegen-openapi";
const config: ConfigFile = {
schemaFile: "https://example.com/api/openapi.json",
apiFile: "./src/store/emptyApi.ts",
apiImport: "emptySplitApi",
outputFile: "./src/store/bahorApi.ts",
exportName: "bahorApi",
hooks: true,
};
export default config;Natija — bahorApi.ts: endpoint'lar, TypeScript turlari va hooklar. Backend o'zgarsa, buyruqni qayta ishga tushirasiz va tur xatolari qayerni tuzatish kerakligini ko'rsatadi. Bu — type-safe kontrakt g'oyasining avtomatik varianti. Biz uni ishga tushirmadik: «Bahor» mashq API'sida OpenAPI hujjati yo'q.
Tekshirib ko'ring:
useGetPageQuery(page)dapage1 → 2 → 1 bo'lsa, nechta so'rov ketadi (kesh muddati tugamagan bo'lsa)?
Javob
Ikkita: getPage(1) va getPage(2). Uchinchi o'tish keshdagi getPage(1) ni oladi. Sukut bo'yicha foydalanilmayotgan yozuv 60 soniya saqlanadi (keepUnusedDataFor); undan keyin qaytsangiz — yangi so'rov.
6. Kanon tajribasi: to'rt yo'l, bir vazifa (nega kanonga kirmadi)
vazifalar da server state TanStack Query'da qoladi (holat 14, TanStack Query I–III). Bu qaror to'rt variantni bir xil vazifada sinab ko'rib qabul qilingan: ro'yxatni serverdan yuklash, POST bilan qo'shish, ro'yxatni yangilash. To'rtalasi «Bahor» API'si bilan Chrome 155 da ishladi (yuqoridagi 16-17-server-ishlaydi.txt — hamma variantda GET, POST, OPTIONS, GET). Hajmi (tajriba/hajm.txt, React asosi bilan):
- qo'lda (fetch + useState)68 001 B, 35 qator66,4 KB
- SWR 2.5.173 381 B, 16 qator71,7 KB
- TanStack Query 5.104.178 333 B, 21 qator76,5 KB
- RTK Query (RTK 2.13.0)92 813 B, 35 qator90,6 KB
Manba: IlmHamroh kurs tajribasi (Vite 8.3.3, gzip -9, 2026-10-13)
Qo'lda yozilgan variantga nisbatan: SWR +5.4 KB, TanStack Query +10.3 KB, RTK Query +24.8 KB. (vazifalar ning o'zida Query +9.9 KB chiqqan: u yerda qo'lda yozilgan kodning bir qismi o'chirildi — TanStack Query I.) RTK Query'ning katta qismi — Redux'ning o'zi: configureStore, react-redux, Immer. Redux allaqachon bor loyihada bu narx to'lab bo'lingan — RTK Query'ning o'zi qo'shadigani ancha kichik.
Nega kanonga kirmadi:
vazifalarRedux'da emas. RTK Query uchun Redux'ni olib kirish kerak bo'lardi: +24.8 KB va ikkinchi holat tizimi (client state Zustand'da).- TanStack Query kichikroq va 18-qismdagi qo'lda kodni (pult, "kutilmoqda", tarmoq holati) bitta
queryKeyga almashtirdi: 7 qadamda 4 GET → 2 GET (TanStack Query II dagi o'lchov). - Optimistik qatlam kanonda React'ning
useOptimisticida — ikkala kutubxona bilan ham bir xil ishlaydi.
Qachon RTK Query — to'g'ri tanlov:
- Loyiha Redux'da. Bitta store, bitta DevTools, so'rovlar ham Redux amallari sifatida jurnalda. Ikkinchi kesh kutubxonasini qo'shmaysiz.
- Ko'p endpoint'li API va OpenAPI hujjati bor — codegen bilan butun qatlam generatsiya qilinadi.
- Server ma'lumoti va client state bir-biriga chambarchas: listener middleware (Redux Toolkit II) RTK Query amallariga ham quloq soladi (masalan,
getMenu.matchFulfilled).
SWR — eng kichik, oddiy "yukla va keshla" uchun yaxshi. Lekin mutatsiyalar va bekor qilishda TanStack Query'dan kamroq imkoniyat beradi (TanStack Query V darsida taqqoslangan).
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
Warning: Middleware for RTK-Query API at reducerPath "api" has not been added to the store. |
api.middleware ulanmagan |
getDefault().concat(api.middleware) |
hook undefined yoki "not a function" |
endpoint nomi va hook nomi mos emas | getMenu → useGetMenuQuery |
| mutatsiyadan keyin ro'yxat eski | providesTags/invalidatesTags yo'q yoki teg nomlari farq qiladi |
ikkala tomonda bir xil teg; tagTypes da e'lon |
| bitta o'zgarish hamma narsani qayta yuklaydi | faqat umumiy teg (["Taom"]) |
{ type: "Taom", id } |
ikki createApi — ikki kesh, teglar ishlamaydi |
har fayl o'z API'sini yaratgan | bitta createApi + injectEndpoints |
| 401 dan keyin hamma so'rov yiqiladi | token yangilash yo'q | baseQuery o'rami (401 → yangilash → qayta) |
isLoading yangi sahifada false, lekin ma'lumot eski |
isLoading — faqat birinchi yuklash |
isFetching ni tekshiring |
8. Mashqlar
Mashqlar kurs/mashqlar/19/25-rtk-query/ da. 2- va 3-mashq Node'da (o'z papkangizda @reduxjs/toolkit@2.13.0, .mjs); soxta fetch misollardagidek.
1-mashq (oson): teglarni qo'ying
Mutatsiyadan keyin getOrders o'zi qayta yuklanishi uchun bo'sh joylarni to'ldiring:
const ordersApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: "/api/" }),
tagTypes: ["Buyurtma"],
endpoints: (build) => ({
getOrders: build.query({
query: () => "buyurtmalar",
[___:providesTags]: ["Buyurtma"],
}),
addOrder: build.mutation({
query: (body) => ({ url: "buyurtmalar", method: "POST", body }),
[___:invalidatesTags]: ["Buyurtma"],
}),
}),
});Avtomatik hooklarning nomlari: [:useGetOrdersQuery] va [:useAddOrderMutation].
Yechim
providesTags — so'rov "men shu tegli ma'lumotni beraman" deydi; invalidatesTags — mutatsiya "shu teg eskirdi" deydi. Nomlar: use + GetOrders + Query → useGetOrdersQuery; use + AddOrder + Mutation → useAddOrderMutation.
2-mashq (o'rta): faqat o'zgargan taom
«id li teglar» bo'limidagi misolni o'zingiz yozing va tekshiring: ikki taom yuklanadi, 1-taom narxi o'zgaradi — qayta faqat GET 1 ketsin, GET 2 emas. Oxirida 1-taomning yangi narxini chiqaring.
Yechim
Yechim — «id li teglar» bo'limidagi kodning o'zi (kurs/mashqlar/19/25-rtk-query/2-yechim.mjs). Natija:
so'rov: GET 1
so'rov: GET 2
--- 1-taom narxi o'zgaradi
so'rov: PATCH 1
so'rov: GET 1
38000Agar invalidatesTags: ["Taom"] (umumiy) yozsangiz, PATCH dan keyin ikkita GET ketadi — GET 1 va GET 2. Tekshirib ko'ring: farq sonda ko'rinadi.
3-mashq (qiyin): optimistik savat
Savat (getCart, boshida ["Osh"]) va addItem(name) mutatsiyasi. Server "Lag'mon" ni rad etadi (500). Talab:
onQueryStartedda taomni keshga darhol qo'shing (cartApi.util.updateQueryData) vadarhol: <nom>deb yozing.await queryFulfilledxato bersa —patch.undo()vaqaytarildi: <nom>.- Manti va Lag'mon qo'shilgandan keyin keshni chiqaring.
Ishora: getCart serverga bormasin — queryFn: () => ({ data: ["Osh"] }) (so'rov o'rniga tayyor qiymat).
Yechim
import { configureStore } from "@reduxjs/toolkit";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query";
// soxta server: "Lag'mon" ni saqlashni rad etadi
globalThis.fetch = async (request) => {
const { name } = await request.json();
if (name === "Lag'mon") {
return Response.json({ xato: "band" }, { status: 500 });
}
return Response.json({ name }, { status: 201 });
};
const cartApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: "https://example.com/api/" }),
endpoints: (build) => ({
getCart: build.query({ queryFn: () => ({ data: ["Osh"] }) }),
addItem: build.mutation({
query: (name) => ({
url: "savat",
method: "POST",
body: { name },
}),
async onQueryStarted(name, { dispatch, queryFulfilled }) {
const add = (draft) => {
draft.push(name);
};
const patch = dispatch(
cartApi.util.updateQueryData("getCart", undefined, add),
);
console.log("darhol:", name);
try {
await queryFulfilled;
} catch {
patch.undo();
console.log("qaytarildi:", name);
}
},
}),
}),
});
const store = configureStore({
reducer: { [cartApi.reducerPath]: cartApi.reducer },
middleware: (getDefault) =>
getDefault().concat(cartApi.middleware),
});
const { getCart, addItem } = cartApi.endpoints;
await store.dispatch(getCart.initiate());
await store.dispatch(addItem.initiate("Manti"));
await store.dispatch(addItem.initiate("Lag'mon"));
console.log(getCart.select()(store.getState()).data);Node 24, RTK 2.13.0:
darhol: Manti
darhol: Lag'mon
qaytarildi: Lag'mon
[ 'Osh', 'Manti' ]Lag'mon ham bir lahza keshda bo'ldi ("darhol"), server rad etgach — olib tashlandi. Bekor qilishda patch.undo() Immer yozib qo'ygan teskari o'zgarishni qo'llaydi. Shuning uchun bu oraliqda kelgan boshqa o'zgarishlar (masalan, manti) buzilmaydi. useOptimistic dagi g'oya, faqat kesh Redux'da.
4-mashq (tahlil): qaysi biri?
Har loyiha uchun tanlang — RTK Query, TanStack Query yoki SWR — va bir gap bilan asoslang:
- Bank ilovasi, 5 yillik, Redux'da, 180 ta REST endpoint, backend OpenAPI beradi.
- «Bahor» sayti: Vite + React, holat Zustand'da, 6 ta endpoint, optimistik savat.
- Blog: faqat maqolalarni o'qish, yozish yo'q, hajm har kilobaytda muhim.
Yechim
- RTK Query — Redux allaqachon bor (narxi to'langan), codegen 180 endpoint'ni yasaydi, bitta store va DevTools.
- TanStack Query — Redux yo'q; +10.3 KB, mutatsiya va optimistik yangilash qulay. Bu aynan
vazifalarqarori. - SWR — eng kichik (+5.4 KB), faqat o'qish uchun yetarli. TanStack Query ham xato emas, lekin bu yerda ortiqcha.
9. Real ishda
- Redux loyihalarida yangi server kodining standarti — RTK Query; eski thunk +
statuskodlari bosqichma-bosqich unga ko'chiriladi. injectEndpoints— katta ilovada bittacreateApi(bitta kesh) va har modul o'z endpoint'larini alohida faylda "ulaydi". Codegen ham shu usulda ishlaydi (emptySplitApi).- Monitoring: RTK Query amallari (
api/executeQuery/pending…) Redux DevTools'da ko'rinadi — so'rovlar tarixi holat tarixi bilan bir joyda. - Intervyu savollari: "RTK Query va TanStack Query farqi?", "teglar qanday ishlaydi?", "token eskirganda qayta so'rashni qanday qilasiz?", "
isLoadingvaisFetchingfarqi?".
Xulosa
- RTK Query — Redux Toolkit ichidagi server state vositasi:
createApi+baseQuery+endpoints; reducer va middleware store'ga ulanadi. - Har endpoint'ga avtomatik hook:
useGetMenuQuery,useAddDishMutation; bir xil so'rovlar birlashadi, natija keshda. - Teglar: so'rov
providesTags, mutatsiyainvalidatesTags; aniq yangilash uchun{ type, id }. - Auth —
prepareHeadersva 401 da yangilaydiganbaseQueryo'rami; sahifalash — argument so'rov kalitining bir qismi; codegen — OpenAPI'dan. - Tajriba: RTK Query +24.8 KB (Redux bilan), TanStack Query +10.3 KB, SWR +5.4 KB. Redux loyihasida — RTK Query,
vazifalarkabi Redux'siz loyihada — TanStack Query.
Keyingi dars: Atom va proxy state: Jotai, Valtio, MobX — holatni mayda atomlarga bo'lish, "o'zgartirib yoz" proxy modeli va signals g'oyasi; tajriba o'lchovlari bilan.
Manbalar
- Redux Toolkit: RTK Query Overview,
createApi,fetchBaseQuery— redux-toolkit.js.org/rtk-query (v2.13, 2026-10 holatiga) - RTK Query: Automated Re-fetching (tags), Manual Cache Updates (
onQueryStarted,updateQueryData) — redux-toolkit.js.org/rtk-query/usage - RTK Query: Customizing queries (re-authorization), Pagination, Infinite Queries, Code Generation — redux-toolkit.js.org/rtk-query/usage
- TanStack Query: Comparison (React Query vs RTK Query vs SWR) — tanstack.com/query/latest/docs/framework/react/comparison
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!