IlmHamroh
JavaScript Full-stack/19-qism. React: routing, server state, formalar17/36-dars21 daqiqa
Mundarija (30)

TanStack Query prefetch va Suspense: oldindan yuklash, bekor qilish va SWR

Qisqacha: Ma'lumotni foydalanuvchi so'rashidan oldin olib qo'ysa bo'ladi: prefetchQuery (sichqoncha havola ustiga kelganda) yoki router loader'idagi ensureQueryData (sahifa chizilishidan oldin). useSuspenseQuery kutishni <Suspense> ga topshiradi — komponentda data doim bor. queryFn ga berilgan signal keraksiz so'rovni bekor qiladi. SWR — xuddi shu g'oyaning yengilroq (+5.4 KB), lekin imkoniyati kamroq muqobili.

Bu darsda

  • prefetchQuery bilan havola ustiga kelganda ma'lumotni oldindan yuklaysiz.
  • React Router loader'ida ensureQueryData va fetchQuery farqini bilasiz.
  • useSuspenseQuery bilan <Suspense> fallback'ini ishlatasiz va useQuery dan farqini ko'rasiz.
  • signal bilan so'rov qachon bekor qilinishini konsolda kuzatasiz; retry oraliqlarini hisoblaysiz.
  • DevTools va kesh saqlash (persist) nima berishini, SWR qachon yetarli ekanini bilasiz.

Oldin bilishingiz kerak: TanStack Query IV: sahifalash, TanStack Query II: kesh hayot sikli, Suspense, React Router IV: data mode, AbortController.

1. Nega bu kerak?

Hozirgacha so'rov foydalanuvchi sahifani ochgandan keyin ketardi. Mehmon «Osh» ni bosadi → sahifa ochiladi → "Yuklanmoqda…" → 300 ms → taom. Bu 300 ms ni yo'qotish mumkinmi?

Mumkin, chunki foydalanuvchi bosishdan oldin "ogohlantiradi". Sichqoncha havolaga yetib kelishi va bosish orasida o'rtacha 100–300 ms o'tadi. Telefonda barmoq tegishi va qo'yib yuborish orasida ham vaqt bor. Shu oraliqda so'rovni boshlasak, bosilganda ma'lumot tayyor yoki yarim yo'lda bo'ladi.

O'xshatish: tajribali ofitsiant. Mehmon menyuni varaqlab, osh sahifasida to'xtadi. Ofitsiant oshxonaga "osh tayyorlab turinglar" deb aytib qo'yadi. Mehmon buyurtma berganda — osh tayyor. Aytmagan bo'lsa ham hech narsa yo'qotilmaydi: osh boshqa mehmonga ketadi (kesh).

Uchinchi muammo — teskarisi: keraksiz so'rov. Mehmon «Osh» ni bosdi, fikrini o'zgartirib «Manti» ni bosdi. Osh so'rovi hamon yo'lda — trafik va server vaqti. Uni bekor qilish kerak.

2. Oldindan yuklash: prefetchQuery

2.1 Keshni oldindan to'ldirish

queryClient.prefetchQuery(sozlama) — useQuery kabi so'raydi va keshga yozadi, lekin hech qanday komponent unga obuna emas. Natijani qaytarmaydi, xato ham tashlamaydi — faqat keshni to'ldiradi. Kesh yangi bo'lsa (staleTime ichida) — hech narsa qilmaydi.

Haqiqiy loyihada prefetch havolaning onMouseEnter va onFocus hodisalarida chaqiriladi. Natija oynasining tekshiruvchisi sichqonchani "olib bora" olmaydi, shuning uchun bu misolda hover o'rniga alohida tugma:

jsx
import { useState } from "react";
import {
  QueryClient,
  QueryClientProvider,
  queryOptions,
  useQuery,
  useQueryClient,
} from "@tanstack/react-query";

const queryClient = new QueryClient({
  defaultOptions: { queries: { staleTime: 60_000 } },
});

function fetchDish(slug) {
  console.log("so'rov:", slug);
  return new Promise((resolve) =>
    setTimeout(() => resolve({ slug, price: 35000 }), 400),
  );
}

const dishQuery = (slug) =>
  queryOptions({
    queryKey: ["dish", slug],
    queryFn: () => fetchDish(slug),
  });

function Dish({ slug }) {
  const { isPending, data } = useQuery(dishQuery(slug));
  if (isPending) return <p>Yuklanmoqda…</p>;
  return <p>{data.slug}: {data.price} so'm</p>;
}

function Menu() {
  const client = useQueryClient();
  const [open, setOpen] = useState(null);
  const prefetch = () => client.prefetchQuery(dishQuery("osh"));
  return (
    <>
      <button onClick={prefetch}>Ustiga keldi (hover)</button>{" "}
      <button onMouseEnter={prefetch} onClick={() => setOpen("osh")}>
        Osh
      </button>
      {open && <Dish slug={open} />}
    </>
  );
}

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Menu />
    </QueryClientProvider>
  );
}

Konsolda:

text
so'rov: osh

Bu misolda soxta server sekinroq — 400 ms. Ekranda:

text
Ustiga keldi (hover) Osh
osh: 35000 so'm

«Osh» bosilganda so'rov yo'q va "Yuklanmoqda…" ham yo'q. Oraliq nuqta narxni bosishdan bir zum keyin tekshiradi — 400 ms lik server javobidan ancha oldin u ekranda. U allaqachon keshda edi. «Ishga tushir» dan keyin o'zingiz sinab ko'ring: birinchi tugmani bosmasdan, sichqonchani «Osh» ustiga olib boring — onMouseEnter xuddi shu ishni qiladi.

Uch qoida:

  • Kalit va funksiya — o'sha. Prefetch boshqa kalit bilan qilinsa, kesh to'ladi, lekin sahifa uni topmaydi. queryOptions funksiyasi (TanStack Query II) bu xatoni yo'qotadi: ikkala joy dishQuery("osh") ni oladi.
  • staleTime kerak. Sukutdagi 0 da prefetch qilingan ma'lumot darhol eskiradi va sahifa ochilganda yana so'raladi (keshdan ko'rsatib, fonda). Ikki so'rov — bittasi ortiqcha.
  • Klaviatura ham. onFocus ni unutmang — Bobur sichqonchasiz, Tab bilan yuradi.

2.2 Qachon prefetch qilmaslik kerak

Prefetch — "ehtimol kerak bo'ladi" degan taxmin. Noto'g'ri taxmin — isrof:

  • ro'yxatdagi 50 ta havolaning har biri ustidan sichqoncha o'tib ketadi — 50 ta so'rov;
  • telefonda trafik pulli, internet sekin;
  • ma'lumot og'ir (rasm, katta jadval) yoki server qimmat hisoblaydi.

Oddiy yo'l: hover'da kichik kechikish (100 ms ichida sichqoncha ketib qolsa — so'ramaslik) yoki faqat eng ehtimolli havolalarda.

3. Router loader va kesh

3.1 ensureQueryData — loader'da

React Router IV: data mode darsida loader sahifa chizilishidan oldin ma'lumot olishini ko'rdik. Loader ham bir xil "oldindan yuklash": havola bosildi → loader ishladi → sahifa ma'lumot bilan chizildi.

Loader va Query birga ishlaganda savol tug'iladi: ma'lumot qayerda saqlanadi? Javob — Query keshida. Loader keshni to'ldiradi, komponent keshdan o'qiydi. ensureQueryData — "keshda bo'lsa — uni ber, bo'lmasa — so'ra va keshga yoz":

jsx
import {
  createMemoryRouter,
  Link,
  RouterProvider,
  useParams,
} from "react-router";
import {
  QueryClient,
  QueryClientProvider,
  queryOptions,
  useSuspenseQuery,
} from "@tanstack/react-query";

const queryClient = new QueryClient();

function fetchDish(slug) {
  console.log("so'rov:", slug);
  return new Promise((resolve) =>
    setTimeout(() => resolve({ name: slug, price: 35000 }), 200),
  );
}

const dishQuery = (slug) =>
  queryOptions({
    queryKey: ["dish", slug],
    queryFn: () => fetchDish(slug),
  });

function Dish() {
  const { slug } = useParams();
  const { data } = useSuspenseQuery(dishQuery(slug));
  return (
    <>
      <p>
        {data.name}: {data.price} so'm
      </p>
      <Link to="/">Menyu</Link>
    </>
  );
}

const router = createMemoryRouter([
  { path: "/", element: <Link to="/taom/osh">Osh</Link> },
  {
    path: "/taom/:slug",
    element: <Dish />,
    loader: ({ params }) =>
      queryClient.ensureQueryData(dishQuery(params.slug)),
  },
]);

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <RouterProvider router={router} />
    </QueryClientProvider>
  );
}

Konsolda:

text
so'rov: osh

Ekranda:

text
osh: 35000 so'm
Menyu

Ikki marta osh sahifasiga kirdik, so'rov — bitta. Birinchi marta loader so'radi va router javobni kutib, keyin sahifani chizdi. Ikkinchi marta ensureQueryData keshda ma'lumotni topdi va darhol qaytardi.

Komponentda useSuspenseQuery — keyingi bo'limning mavzusi. Hozircha: loader keshni to'ldirgan, shuning uchun komponent hech qachon kutmaydi va data doim bor.

Bu misolda ham manzil satri yo'q natija oynasi uchun xotiradagi router (createMemoryRouter) ishlatildi. Loyihada — createBrowserRouter, qolgan kod bir xil.

3.2 ensureQueryData va fetchQuery

Loader uchun ikki metod bor. Farqi — eskirgan ma'lumotda:

Metod Keshda yangi Keshda eskirgan Keshda yo'q
ensureQueryData beradi beradi (so'ramaydi) so'raydi, kutadi
fetchQuery beradi so'raydi, kutadi so'raydi, kutadi
prefetchQuery — so'raydi so'raydi

ensureQueryData — eng tez sahifa o'tishi: keshda nima bo'lsa, shu. Eskirgan bo'lsa ham. Komponentdagi useQuery/useSuspenseQuery keyin uni fonda yangilaydi. revalidateIfStale: true bilan ensureQueryData o'zi ham fonda yangilashni boshlaydi.

fetchQuery — eskirgan bo'lsa yangisini kutadi. vazifalar dagi vazifa sahifasi loader'i shuni ishlatadi (TanStack Query II «Vazifalar qadami»da ko'rdik): vazifa boshqa qurilmada o'chirilgan bo'lishi mumkin. Loader 404 ni to'g'ri qaytarishi uchun 30 soniyadan eski ma'lumotga ishonmaydi.

4. useSuspenseQuery

4.1 Kutish — Suspense'ga

Suspense darsida <Suspense fallback> ichidagi komponent "to'xtab" turishi va React fallback'ni ko'rsatishini o'rgandik. useSuspenseQuery — shu mexanizmni Query bilan ulaydi. Ma'lumot keshda yo'q bo'lsa, komponent to'xtaydi (ichida Promise tashlanadi) va eng yaqin <Suspense> fallback'i chiqadi:

jsx
import { Suspense } from "react";
import {
  QueryClient,
  QueryClientProvider,
  useSuspenseQuery,
} from "@tanstack/react-query";

const queryClient = new QueryClient();

function fetchMenu() {
  console.log("so'rov ketdi");
  return new Promise((resolve) =>
    setTimeout(() => resolve(["Osh", "Manti"]), 200),
  );
}

function Menu() {
  const { data } = useSuspenseQuery({
    queryKey: ["menu"],
    queryFn: fetchMenu,
  });
  // isPending tekshiruvi yo'q — data doim bor
  return <p>{data.join(", ")}</p>;
}

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <h3>«Bahor» menyusi</h3>
      <Suspense fallback={<p>Yuklanmoqda…</p>}>
        <Menu />
      </Suspense>
    </QueryClientProvider>
  );
}

Konsolda:

text
so'rov ketdi

Ekranda:

text
«Bahor» menyusi
Osh, Manti

useQuery dan farqlar:

useQuery useSuspenseQuery
data turi T | undefined T — doim bor
kutish isPending — komponent o'zi <Suspense fallback>
xato isError — komponent o'zi eng yaqin Error Boundary'ga tashlanadi
enabled, placeholderData bor yo'q

Kutish ham, xato ham komponentdan chiqib ketadi. Komponent faqat "baxtli yo'l" ni yozadi. Xatoni Error boundary ushlaydi. «Qayta urinish» uchun Query'da QueryErrorResetBoundary (yoki useQueryErrorResetBoundary) bor: boundary qayta tiklanganda xato bergan so'rovlar ham qaytadan ketadi.

enabled nega yo'q? useSuspenseQuery va'dasi — "data doim bor". O'chirilgan so'rovda ma'lumot bo'lmasdi. Bog'liq so'rovlarni Suspense bilan ketma-ket komponentlarga bo'lib yoziladi: biri to'xtaydi, keyin ichkisi.

4.2 Sharshara tuzog'i

Bitta komponentda ikkita useSuspenseQuery — ular parallel emas. Birinchisi to'xtaydi, komponent qolgan kodni bajarmaydi, ikkinchi so'rov faqat birinchisi tugagach boshlanadi. Bu TanStack Query II darsidagi sharshara — faqat endi ko'zga ko'rinmaydi.

Davosi:

  • ikkalasini bitta useSuspenseQueries ga yig'ish (useQueries ning Suspense ko'rinishi);
  • yoki loader'da ikkalasini oldindan parallel boshlash: Promise.all([ensureQueryData(a), ensureQueryData(b)]).

Tekshirib ko'ring: Loader ensureQueryData bilan keshni to'ldirgan. Sahifa komponenti useSuspenseQuery ishlatadi. Fallback qachon ko'rinadi?

Javob

Odatda hech qachon. Router sahifani loader tugagach chizadi — ma'lumot allaqachon keshda, komponent to'xtamaydi. Fallback faqat kesh shu orada tozalangan bo'lsa (gcTime o'tgan, removeQueries chaqirilgan) ko'rinadi. Shuning uchun loader + useSuspenseQuery — mashhur juftlik: loader tezlik beradi, useSuspenseQuery esa data turini toza qiladi.

5. Bekor qilish va qayta urinish

5.1 signal — qachon bekor qilinadi

TanStack Query I da queryFn kontekstida signal borligini aytgan edik. Query uni qachon bekor qiladi? Ikki holatda:

  1. so'rovni hech kim kutmay qoldi — komponent yopildi yoki kalit o'zgardi va shu kalitga boshqa kuzatuvchi yo'q;
  2. siz o'zingiz cancelQueries ni chaqirdingiz.

Muhim shart: Query faqat signal ishlatilgan bo'lsa bekor qiladi. queryFn uni o'qimasa, Query "bu funksiya bekor qilishni bilmaydi" deb hisoblaydi va so'rovni oxirigacha kutadi (natijani keshga ham yozadi).

Mehmon tez-tez taom almashtiradi:

jsx
import { useState } from "react";
import {
  QueryClient,
  QueryClientProvider,
  useQuery,
} from "@tanstack/react-query";

const queryClient = new QueryClient();

function fetchDish(slug, signal) {
  console.log("boshlandi:", slug);
  signal.addEventListener("abort", () =>
    console.log("bekor qilindi:", slug),
  );
  return new Promise((resolve) =>
    setTimeout(() => resolve(`${slug} tayyor`), 500),
  );
}

function Dish({ slug }) {
  const { data } = useQuery({
    queryKey: ["dish", slug],
    queryFn: ({ signal }) => fetchDish(slug, signal),
  });
  return <p>{data ?? "Yuklanmoqda…"}</p>;
}

function Picker() {
  const [slug, setSlug] = useState("osh");
  return (
    <>
      <button onClick={() => setSlug("osh")}>Osh</button>{" "}
      <button onClick={() => setSlug("manti")}>Manti</button>
      <Dish slug={slug} />
    </>
  );
}

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Picker />
    </QueryClientProvider>
  );
}

Konsolda:

text
boshlandi: osh
boshlandi: manti
bekor qilindi: manti
boshlandi: osh
bekor qilindi: osh
boshlandi: manti

Ekranda:

text
Osh Manti
manti tayyor

Qatorma-qator:

Qadam Voqea
ochilish osh so'raldi va 500 ms da keldi (server ataylab sekin — bosishlar javobdan oldin ulgursin)
«Manti» manti so'raldi
100 ms dan keyin «Osh» manti hali yo'lda va endi hech kim kutmaydi → bekor. Osh keshda bor, lekin eskirgan → fonda yangilash
50 ms dan keyin «Manti» osh yangilanishi bekor; manti — yangidan

Bu soxta serverda signal faqat konsolga yozadi. Haqiqiy kodda uni fetch ga berasiz va brauzer so'rovni haqiqatan to'xtatadi: fetch(url, { signal }). DevTools Network panelida bunday so'rov "(canceled)" bo'lib ko'rinadi.

Bekor qilingan so'rov xato emas. Query uni isError ga aylantirmaydi: kesh oldingi holatiga qaytadi. Shuning uchun vazifalar dagi royxatniYukla signal.aborted bo'lsa konsolga "Sinxron bo'lmadi" yozmaydi (TanStack Query I «Vazifalar qadami»).

5.2 retry oraliqlari

TanStack Query I da sukutdagi 3 qayta urinish va 1 s, 2 s, 4 s oraliqlarini aytdik. Bu oraliqlar formula bilan hisoblanadi — har safar ikki baravar, lekin 30 soniyadan oshmaydi. Bu eksponensial kutish (exponential backoff): server og'ir ahvolda bo'lsa, minglab mijoz unga bir vaqtda qayta-qayta urilmaydi.

js
// TanStack Query'ning sukutdagi retryDelay formulasi
const retryDelay = (attempt) =>
  Math.min(1000 * 2 ** attempt, 30000);

for (let attempt = 0; attempt < 7; attempt++) {
  const ms = retryDelay(attempt);
  console.log(`${attempt + 1}-qayta urinishdan oldin: ${ms} ms`);
}

Konsolda:

text
1-qayta urinishdan oldin: 1000 ms
2-qayta urinishdan oldin: 2000 ms
3-qayta urinishdan oldin: 4000 ms
4-qayta urinishdan oldin: 8000 ms
5-qayta urinishdan oldin: 16000 ms
6-qayta urinishdan oldin: 30000 ms
7-qayta urinishdan oldin: 30000 ms

Oltinchidan boshlab 30 s chegarasi ishladi (2 ** 5 * 1000 = 32000 → 30 000). retryDelay ni o'zgartirish mumkin — misollarda natija oynasini kutdirmaslik uchun 200 ms qildik. Internet yo'q paytda urinishlar sanalmaydi: so'rov paused bo'lib turadi (TanStack Query I).

6. DevTools va keshni saqlash

6.1 React Query DevTools

Kesh ko'rinmas narsa. Qaysi kalitlar bor, qaysi biri eskirgan, qachon yangilanadi — konsolga log yozmasdan bilish qiyin. React Query DevTools — sahifa burchagidagi panel: har kalit, uning holati (fresh, stale, fetching, paused, inactive), ichidagi ma'lumot. Undan qo'lda invalidatsiya qilish, xato yoki yuklanishni sun'iy chaqirish ham mumkin.

jsx
// o'z loyihangizda: npm i -D @tanstack/react-query-devtools
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

<QueryClientProvider client={queryClient}>
  <App />
  <ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>;

Paket faqat dev rejimida ishlaydi: production build'da ReactQueryDevtools bo'sh komponentga aylanadi va to'plamga kirmaydi. Natija oynasida bu paket yo'q — shuning uchun misol tekshirilmagan.

TanStack Query II dagi staleTime tanlovi DevTools'da ko'zga ko'rinadi: kalit yashil (fresh) dan sariqqa (stale) qachon o'tishini kuzatasiz.

6.2 Keshni brauzerda saqlash (persist)

Sahifa yangilansa, Query keshi yo'qoladi — u xotirada. Persist — keshni localStorage yoki IndexedDB ga yozib, keyingi ochilishda tiklash. Query'da buning uchun alohida paketlar bor:

jsx
// o'z loyihangizda: @tanstack/react-query-persist-client va
// @tanstack/query-sync-storage-persister
import {
  PersistQueryClientProvider,
} from "@tanstack/react-query-persist-client";
import {
  createSyncStoragePersister,
} from "@tanstack/query-sync-storage-persister";

const queryClient = new QueryClient({
  defaultOptions: { queries: { gcTime: 1000 * 60 * 60 * 24 } },
});
const persister = createSyncStoragePersister({
  storage: window.localStorage,
});

<PersistQueryClientProvider
  client={queryClient}
  persistOptions={{ persister }}
>
  <App />
</PersistQueryClientProvider>;

Ikki nozik joy: gcTime saqlash muddatidan (sukut — 24 soat) kichik bo'lmasin, aks holda tiklangan yozuv darhol o'chadi. Va tiklash tugaguncha so'rovlar kutib turadi.

vazifalar bu paketlarni ishlatmaydi. Unda butun kesh emas, bitta ro'yxat kerak — u 13-qismdan beri localStorage da. Uni initialData qilib berish (TanStack Query II) — sinxron, birinchi kadrdayoq, qo'shimcha paketsiz.

7. SWR — yengilroq muqobil

7.1 SWR nima

SWR — Vercel (Next.js mualliflari) yozgan kichik kutubxona. Nomi HTTP keshlash strategiyasidan: stale-while-revalidate — "eskisini ko'rsat, shu payt yangisini tekshir". Bu strategiyani Kesh strategiyalari va offline ishlash da ham ko'rgansiz. TanStack Query ham xuddi shunday ishlaydi — farq imkoniyatlar soni va hajmda.

Tajribada (tajriba/variantlar/server-swr.tsx, kanondan aynan) o'sha vazifa — ro'yxatni yuklash va qo'shish — SWR bilan:

tsx
// server-swr.tsx — SWR 2 (stale-while-revalidate): useSWR + mutate
import useSWR, { useSWRConfig } from "swr";
import { ishgaTushir } from "./_umumiy.tsx";
import { Korinish, qosh, royxatniOl } from "./_server.tsx";

function Ilova() {
  const { mutate } = useSWRConfig();
  const { data, error, isLoading } = useSWR("vazifalar", () => royxatniOl());
  return (
    <Korinish
      vazifalar={data?.vazifalar}
      yuklanmoqda={isLoading}
      xato={error instanceof Error ? error.message : null}
      onQosh={(matn) => void qosh(matn).then(() => mutate("vazifalar"))}
    />
  );
}
ishgaTushir(<Ilova />);

Tanish narsalar: useSWR(kalit, funksiya) — useQuery ning o'zi, kalit — satr (yoki massiv). mutate("vazifalar") — invalidateQueries ning o'xshashi. Provider shart emas — SWR'da global kesh bor. Yozish uchun alohida hook ham bor (useSWRMutation), lekin bu tajribada oddiy mutate yetdi.

Xuddi shu vazifaning TanStack Query versiyasi (server-query.tsx, aynan):

tsx
function Ilova() {
  const mijoz = useQueryClient();
  const sorov = useQuery({ queryKey: ["vazifalar"], queryFn: ({ signal }) => royxatniOl(signal) });
  const yozuv = useMutation({ mutationFn: qosh, onSettled: () => mijoz.invalidateQueries({ queryKey: ["vazifalar"] }) });
  return (
    <Korinish
      vazifalar={sorov.data?.vazifalar}
      yuklanmoqda={sorov.isPending}
      xato={sorov.error?.message ?? yozuv.error?.message ?? null}
      onQosh={(matn) => yozuv.mutate(matn)}
    />
  );
}

7.2 O'lchov: hajm

To'rtta variant — qo'lda (useEffect + fetch), SWR, TanStack Query va RTK Query — bitta vazifani bajaradi. To'rtalasi Chrome 155 da haqiqiy «Bahor» serveri bilan ishladi: GET → POST → GET (natijalar/tajriba/16-17-server-ishlaydi.txt). Build hajmi (gzip, React bilan birga; natijalar/tajriba/hajm.txt): qo'lda — 68 001 B, SWR — 73 381, Query — 78 333, RTK Query — 92 813. Qo'lda yozilganga nisbatan farq:

Server state kutubxonasining narxi (qo'lda yozilganga qo'shimcha, gzip)
  • qo'lda (useEffect)0 KB
  • SWR 2.5.15,4 KB
  • TanStack Query 5.104.110,3 KB
  • RTK Query 2.13.0Redux bilan birga24,8 KB

Manba: sinov/19/natijalar/tajriba/hajm.txt (vite build, gzip -9, 2026-10-13)

vazifalar ning o'zida Query'ning narxi biroz kamroq chiqdi — +9.9 KB: ilova kodining bir qismi (qo'lda yozilgan "xo'jalik") o'chirildi.

7.3 Qachon qaysi

SWR TanStack Query
hajm ~5 KB ~10 KB
mutatsiya mutate, useSWRMutation useMutation, callback'lar, isMutating
optimistik optimisticData, rollbackOnError onMutate / variables
sahifalash useSWRInfinite useInfiniteQuery, maxPages
oflayn, paused yo'q bor (networkMode)
DevTools rasmiy yo'q bor
router loader qo'lda ensureQueryData, queryOptions

SWR'ni kamsitmang. U to'g'ri tanlov bo'ladi, agar:

  • ilova asosan o'qiydi (blog, katalog, dashboard), yozuv kam;
  • Next.js loyihasi va jamoa Vercel ekotizimida;
  • har kilobayt muhim (sekin internetli mobil auditoriya).

vazifalar uchun Query tanlandi: yozuv ko'p va bir vaqtda (optimistik, isMutating), oflayn rejim (13-qismdan beri — PWA), router loader bilan bitta kesh. Bular SWR'da qo'lda yoziladigan narsalar. RTK Query — loyihada Redux allaqachon bo'lsa (RTK Query darsida).

8. Tajriba: vazifalar da prefetch va Suspense

Bu darsda vazifalar kodi o'zgarmaydi. Har mavzu bo'yicha kanon nima qildi va nega:

  • Ma'lumotni prefetch — kerak emas. Ro'yxat bitta kalitda va hamma sahifa uni ko'radi. TanStack Query II o'lchovi: navigatsiyada 0 so'rov — ma'lumot allaqachon keshda.
  • Kodni prefetch — statistika va sozlamalar sahifalarini route.lazy bilan alohida faylga ajratish sinaldi (natijalar/17-lazy-marshrut.txt). Foyda chiqmadi: sahifalar kodi kichik (0.6 va 0.3 KB gzip). Bundler (Rolldown) umumiy modullarni alohida "vazifa" bo'lagiga ajratdi — ro'yxat sahifasi uni ham yuklaydi. Natija: 130.5 KB va +1 so'rov, lazy'siz — 129.8 KB. Kirmadi.
  • useSuspenseQuery — ro'yxat doim keshda (initialData — localStorage), so'rov hech qachon to'xtatmaydi. Suspense chegarasi hech narsa bermasdi. Suspense darsidagi qaror kuchida.
  • Bekor qilish — bor: queryFn({ signal }) → api.royxat({ signal }) → fetch. Va cancelQueries ikki joyda: yozuv boshlanganda (TanStack Query III) va internet uzilganda (offline hodisasi).
  • SWR va RTK Query — yuqoridagi o'lchov. Kanonga kirmadi.
text
# 19/#17: statistika va sozlamalar sahifalari lazy marshrut (route.lazy + import()) — o'lchov, KANONGA KIRMADI
# Xulosa: sahifalar kodi kichik (0.6 + 0.3 KB gzip); Rolldown umumiy modullarni (vazifa.ts...) alohida 'vazifa' chunk'iga ajratdi — ro'yxat sahifasi uni ham yuklaydi: index 127.0 + vazifa 3.5 = 130.5 KB (lazy'siz index ~129.8 KB) va +1 so'rov. Foyda yo'q — kirmadi.

Qoida: optimallashtirishni o'lchovsiz qilmang. "Lazy — tezroq" degan umumiy gap bu yerda noto'g'ri chiqdi.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
prefetch qilindi, sahifa baribir so'radi staleTime: 0 staleTime ni oshiring
prefetch keshini sahifa topmadi kalit boshqa bitta queryOptions funksiyasi
bitta komponentda ikki Suspense so'rov sekin ketma-ket to'xtaydi (sharshara) useSuspenseQueries yoki loader'da parallel
useSuspenseQuery da enabled ishlamaydi bu sozlama yo'q komponentlarga bo'ling yoki useQuery
xato oq ekran berdi useSuspenseQuery xatosini ushlovchi yo'q Error Boundary + QueryErrorResetBoundary
tez almashtirishda eski so'rovlar tugaydi signal ishlatilmagan fetch(url, { signal })
oynaga qaytganda tiklangan kesh o'chdi persist'da gcTime < maxAge gcTime ni oshiring

10. Mashqlar

Mashqlar kurs/mashqlar/19/17-prefetch/ da.

1-mashq (oson): qaysi metod?

Loader uchun metod tanlang: ensureQueryData yoki fetchQuery.

  • Taom sahifasi: tavsif kuniga bir marta o'zgaradi, sahifa tez ochilsin →
  • Bank o'tkazmasi sahifasi: balans doim eng yangi bo'lsin →
  • vazifalar dagi vazifa sahifasi: o'chirilgan vazifa uchun 404 to'g'ri bo'lsin →
Yechim

ensureQueryData eskirgan keshni ham darhol beradi — tez, lekin eski bo'lishi mumkin. Taom tavsifi uchun bu yaxshi. fetchQuery eskirganini serverdan yangilab, keyin beradi — balans va "bormi-yo'qmi" savoli uchun shu kerak.

2-mashq (o'rta): useQuery dan useSuspenseQuery ga

Komponentni useSuspenseQuery ga o'tkazing: isPending tekshiruvi ketsin, kutish <Suspense fallback={<p>Yuklanmoqda…</p>}> ga o'tsin.

jsx
import {
  QueryClient,
  QueryClientProvider,
  useQuery,
} from "@tanstack/react-query";

const queryClient = new QueryClient();

function fetchTables() {
  return new Promise((resolve) =>
    setTimeout(() => resolve([2, 4, 7]), 200),
  );
}

function FreeTables() {
  const { isPending, data } = useQuery({
    queryKey: ["tables"],
    queryFn: fetchTables,
  });
  if (isPending) return <p>Yuklanmoqda…</p>;
  return <p>Bo'sh stollar: {data.join(", ")}</p>;
}

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <FreeTables />
    </QueryClientProvider>
  );
}

Ekranda:

text
Bo'sh stollar: 2, 4, 7
Yechim
jsx
import { Suspense } from "react";
import {
  QueryClient,
  QueryClientProvider,
  useSuspenseQuery,
} from "@tanstack/react-query";

const queryClient = new QueryClient();

function fetchTables() {
  return new Promise((resolve) =>
    setTimeout(() => resolve([2, 4, 7]), 200),
  );
}

function FreeTables() {
  const { data } = useSuspenseQuery({
    queryKey: ["tables"],
    queryFn: fetchTables,
  });
  return <p>Bo'sh stollar: {data.join(", ")}</p>;
}

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Suspense fallback={<p>Yuklanmoqda…</p>}>
        <FreeTables />
      </Suspense>
    </QueryClientProvider>
  );
}

Ekranda:

text
Bo'sh stollar: 2, 4, 7

Komponent bir qatorga qisqardi: faqat ma'lumot bor holat. "Yuklanmoqda…" endi App da — bir nechta komponent bitta fallback ostida tursa, ular birga chiqadi.

3-mashq (qiyin): loader + kesh

«Bahor» stol sahifasi: /stol/:id. Yozing:

  1. tableQuery(id) — queryOptions, kalit ["table", id], fetchTable(id) dan.
  2. Marshrut loader'i — ensureQueryData(tableQuery(params.id)).
  3. Table komponenti — useSuspenseQuery(tableQuery(id)), ekranda "4-stol: 2 mehmon" va «Orqaga» havolasi (/).
  4. Bosh sahifa (/) — «4-stol» havolasi.
  5. 4-stolga ikki marta kirilganda serverga bitta so'rov.

Server:

jsx
const GUESTS = { 4: 2, 7: 5 };

function fetchTable(id) {
  console.log("so'rov: stol", id);
  return new Promise((resolve) =>
    setTimeout(() => resolve({ id, guests: GUESTS[id] }), 200),
  );
}

Ishora: «ensureQueryData — loader'da» bo'limidagi misol. params.id — satr (React Router II dagi qoida), GUESTS["4"] ham ishlaydi.

Yechim
jsx
import {
  createMemoryRouter,
  Link,
  RouterProvider,
  useParams,
} from "react-router";
import {
  QueryClient,
  QueryClientProvider,
  queryOptions,
  useSuspenseQuery,
} from "@tanstack/react-query";

const queryClient = new QueryClient();

const GUESTS = { 4: 2, 7: 5 };

function fetchTable(id) {
  console.log("so'rov: stol", id);
  return new Promise((resolve) =>
    setTimeout(() => resolve({ id, guests: GUESTS[id] }), 200),
  );
}

const tableQuery = (id) =>
  queryOptions({
    queryKey: ["table", id],
    queryFn: () => fetchTable(id),
  });

function Table() {
  const { id } = useParams();
  const { data } = useSuspenseQuery(tableQuery(id));
  return (
    <>
      <p>
        {data.id}-stol: {data.guests} mehmon
      </p>
      <Link to="/">Orqaga</Link>
    </>
  );
}

const router = createMemoryRouter([
  { path: "/", element: <Link to="/stol/4">4-stol</Link> },
  {
    path: "/stol/:id",
    element: <Table />,
    loader: ({ params }) =>
      queryClient.ensureQueryData(tableQuery(params.id)),
  },
]);

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <RouterProvider router={router} />
    </QueryClientProvider>
  );
}

Konsolda:

text
so'rov: stol 4

Ekranda:

text
4-stol: 2 mehmon
Orqaga

Ikkinchi kirishda ensureQueryData keshdan oldi. tableQuery loader va komponentda bir xil — kalit adashmaydi. Kalitda id satr ("4"): manzildan kelgani shunday. Agar bir joyda son, boshqa joyda satr bersangiz (["table", 4] va ["table", "4"]), bu ikki xil kalit bo'ladi.

11. Real ishda

  • Hover prefetch — katta saytlarda odatiy: GitHub, Vercel, ko'p internet-do'konlar. Next.js <Link> ko'rinadigan havolalarning kodini o'zi oldindan yuklaydi (Next.js qismida). Ma'lumotni esa siz prefetch qilasiz.
  • Router + Query — TanStack Router va React Router data mode'da loader ichida ensureQueryData — tavsiya etilgan naqsh. Loader tezlikni, Query keshni beradi.
  • Suspense — React'ning yangi API'lari (use, server komponentlar) Suspense atrofida qurilgan. useSuspenseQuery shu yo'nalishga mos.
  • DevTools — "nega bu so'rov ikki marta ketdi?" savoliga eng tez javob. Ishga kirganda birinchi o'rnatadigan narsalardan biri.
  • Intervyu savollari: "prefetch qanday ishlaydi va qachon zararli?", "useSuspenseQuery va useQuery farqi?", "Query so'rovni qachon bekor qiladi?", "SWR va React Query farqi?".

Xulosa

  • prefetchQuery — keshni oldindan to'ldiradi (hover, focus). Bir xil queryOptions va yetarli staleTime bilan.
  • Loader'da: ensureQueryData — keshdagini darhol (eskirgan bo'lsa ham), fetchQuery — eskirganini yangilab.
  • useSuspenseQuery — data doim bor, kutish <Suspense> ga, xato Error Boundary'ga. Bitta komponentda ikkitasi — sharshara.
  • So'rov hech kim kutmay qolganda yoki cancelQueries da bekor qilinadi — faqat signal ishlatilgan bo'lsa. Bekor — xato emas.
  • retry oraliqlari: 1, 2, 4… s, 30 s gacha. DevTools — keshni ko'rish, persist — saqlash.
  • SWR (+5.4 KB) — o'qishga yo'naltirilgan ilovalar uchun yetarli; ko'p yozuv va oflaynda Query kuchliroq.

Keyingi dars: Real-time: WebSocket, SSE va Socket.IO frontendda — server o'zi xabar yuborganda React'ni qanday ulash; vazifalar da boshqa qurilmadagi o'zgarish 254 ms da.

Manbalar

  • TanStack Query v5: Prefetching & Router Integration — tanstack.com/query/v5/docs/framework/react/guides/prefetching (2026-10, v5.104)
  • TanStack Query v5: Suspense, Query Cancellation, Query Retries — …/guides/suspense, …/guides/query-cancellation, …/guides/query-retries
  • TanStack Query v5: Devtools, persistQueryClient — …/devtools, …/plugins/persistQueryClient
  • SWR: Getting Started, Mutation — swr.vercel.app/docs/getting-started (v2.5)
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
TanStack Query prefetch va Suspense: oldindan yuklash, bekor qilish va SWR — IlmHamroh