IlmHamroh
JavaScript Full-stack/19-qism. React: routing, server state, formalar14/36-dars29 daqiqa
Mundarija (34)

TanStack Query kesh: staleTime, gcTime, kalitlar va bog'liq so'rovlar

Qisqacha: Keshdagi ma'lumot ikki xil vaqt bilan yashaydi. staleTime — qancha vaqt "yangi" hisoblanadi: shu muddatda Query serverga qayta so'ramaydi. gcTime — hech kim ishlatmay qolgandan keyin keshda qancha turadi, keyin o'chiriladi. Sukut bo'yicha staleTime: 0 — ma'lumot darhol eskiradi va sahifa ochilganda, oynaga qaytilganda, internet qaytganda fonda yangilanadi.

Bu darsda

  • staleTime va gcTime ni farqlaysiz va ularni o'lchov bilan tanlaysiz.
  • Avtomatik yangilanishlarni (refetchOnMount, refetchOnWindowFocus, refetchOnReconnect) taniysiz va kerak bo'lsa o'chirasiz.
  • So'rov kalitlarini (query key) bitta joyda yig'asiz: kalit fabrikasi va queryOptions.
  • enabled, select, bog'liq va parallel so'rovlar, useQueries bilan ishlaysiz.
  • vazifalar da 7 qadamdagi so'rovlar sonini 4 tadan 2 taga tushirasiz.

Oldin bilishingiz kerak: TanStack Query I: useQuery asoslari, Server state vs client state, React Router IV: data mode.

1. Nega bu kerak?

Oldingi darsda bir narsani ko'rdik: «Osh» ga qaytganda narx keshdan darhol chiqdi, lekin konsolda yana so'rov: osh bor edi. Query keshdagini ko'rsatdi va baribir serverga so'radi.

Bu yaxshimi? Vaziyatga bog'liq. «Bahor» menyusi kuniga bir marta o'zgaradi — mehmon har bosganda serverga so'rash isrof. Oshxonadagi "tayyor buyurtmalar" ekrani esa har soniyada o'zgaradi — u yerda 30 soniyalik eski ma'lumot xato.

Menyu doskasi o'xshatishini davom ettiramiz. Doskaga ikki yorliq yopishtiramiz. Birinchisi — "yangilik muddati": shu vaqtgacha yozuvga ishonsa bo'ladi. Ikkinchisi — "o'chirish muddati": bu bo'limga hech kim qaramay qo'ysa, qachon o'chiriladi. TanStack Query'da bu ikki yorliq — staleTime va gcTime.

2. Kesh hayot sikli

2.1 Yangi va eskirgan

Keshdagi har yozuv ikki holatdan birida bo'ladi:

  • yangi (fresh) — ishonsa bo'ladi. Query serverga so'ramaydi, keshdagini beradi.
  • eskirgan (stale) — ko'rsatiladi, lekin birinchi qulay paytda fonda yangilanadi.

staleTime — ma'lumot kelgandan keyin qancha millisekund yangi turishi. Sukut — 0: kelgan zahoti eskiradi. Shuning uchun oldingi darsda «Osh» ga qaytganda so'rov ketdi.

Sinab ko'ramiz. Menyuni yopib, qayta ochamiz (Menu olib tashlanadi va qayta qo'yiladi):

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

const queryClient = new QueryClient();

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

function Menu() {
  const { isPending, data } = useQuery({
    queryKey: ["menu"],
    queryFn: fetchMenu,
  });
  return <p>{isPending ? "Yuklanmoqda…" : data.join(", ")}</p>;
}

function Page() {
  const [open, setOpen] = useState(true);
  return (
    <>
      <button onClick={() => setOpen(false)}>Yopish</button>{" "}
      <button onClick={() => setOpen(true)}>Ochish</button>
      {open && <Menu />}
    </>
  );
}

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

Konsolda:

text
so'rov ketdi
so'rov ketdi

Ekranda:

text
Yopish Ochish
Osh, Manti

Qayta ochganda "Yuklanmoqda…" chiqmadi — menyu keshdan darhol keldi. Lekin so'rov baribir ketdi. Endi bitta qator qo'shamiz: staleTime: 30_000 (30 soniya). Raqamdagi _ — faqat o'qish uchun ajratuvchi, 30_000 va 30000 bir xil son.

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

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

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

function Menu() {
  const { isPending, data } = useQuery({
    queryKey: ["menu"],
    queryFn: fetchMenu,
  });
  return <p>{isPending ? "Yuklanmoqda…" : data.join(", ")}</p>;
}

function Page() {
  const [open, setOpen] = useState(true);
  return (
    <>
      <button onClick={() => setOpen(false)}>Yopish</button>{" "}
      <button onClick={() => setOpen(true)}>Ochish</button>
      {open && <Menu />}
    </>
  );
}

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

Konsolda:

text
so'rov ketdi

Ekranda:

text
Yopish Ochish
Osh, Manti

Bitta so'rov. 30 soniya ichida menyuni necha marta ochsangiz ham, serverga murojaat yo'q. 30 soniyadan keyin ochsangiz — keshdagi darhol ko'rinadi va fonda yangilanadi. "Yuklanmoqda…" hech qachon qaytmaydi.

staleTime ni ikki joyda berish mumkin: QueryClient ning defaultOptions ida (hamma so'rov uchun) yoki bitta useQuery da (faqat shu so'rov uchun). Ikkinchisi birinchisidan ustun.

2.2 gcTime — qachon o'chiriladi

Endi teskari savol. Menyuni yopdik va hech kim uni ko'rsatmayapti. Kesh uni abadiy saqlaydimi? Yo'q. Yozuvni ishlatadigan komponent qolmasa, u faol emas (inactive) bo'ladi. Shu paytdan gcTime sanaladi. Muddat tugasa — yozuv keshdan o'chiriladi. Nomi "garbage collection" (axlat yig'ish) dan — Xotira boshqaruvi va garbage collection darsidagi tushuncha. Sukut — 5 daqiqa.

Ko'rsatish uchun gcTime: 300 (0.3 s) qilamiz. Yopamiz, 0.6 s kutamiz, ochamiz:

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

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

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

function Menu() {
  const { isPending, data } = useQuery({
    queryKey: ["menu"],
    queryFn: fetchMenu,
  });
  return <p>{isPending ? "Yuklanmoqda…" : data.join(", ")}</p>;
}

function Page() {
  const [open, setOpen] = useState(true);
  return (
    <>
      <button onClick={() => setOpen(false)}>Yopish</button>{" "}
      <button onClick={() => setOpen(true)}>Ochish</button>
      {open && <Menu />}
    </>
  );
}

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

Konsolda:

text
so'rov ketdi
so'rov ketdi

Ekranda:

text
Yopish Ochish
Osh, Manti

staleTime 30 s bo'lsa ham, ikkinchi so'rov ketdi va "Yuklanmoqda…" qaytdi. Chunki yozuv o'chib ketgan edi — "yangi" yoki "eskirgan" deyishga hech narsa qolmagan. Qoida: gcTime ni staleTime dan kichik qilmang. Aks holda "30 s yangi" va'dasi bajarilmaydi.

Butun hayot sikli bitta rasmda:

stateDiagram-v2
  [*] --> Yangi: javob keldi
  Yangi --> Eskirgan: staleTime o'tdi
  Eskirgan --> Yangi: fonda yangilandi
  Yangi --> Faolsiz: hech kim ishlatmaydi
  Eskirgan --> Faolsiz: hech kim ishlatmaydi
  Faolsiz --> Yangi: yana ishlatildi
  Faolsiz --> [*]: gcTime o'tdi — o'chirildi

Rasmda bitta nozik joy bor: faolsiz yozuv yana ishlatilsa, u eskirgan bo'lsa yangilanadi, yangi bo'lsa — so'rovsiz beriladi.

Sozlama Sukut Nimani hal qiladi
staleTime 0 qachon fonda qayta so'rash kerak
gcTime 5 daqiqa faolsiz yozuv qachon o'chiriladi

Tekshirib ko'ring: staleTime: 60_000, gcTime sukutda (5 daq). Foydalanuvchi menyuni ochdi, 2 daqiqadan keyin boshqa sahifaga o'tdi, yana 1 daqiqadan keyin qaytdi. Ekranda "Yuklanmoqda…" bo'ladimi? Serverga so'rov ketadimi?

Javob

"Yuklanmoqda…" bo'lmaydi: yozuv faolsiz bo'lganiga 1 daqiqa, gcTime 5 daqiqa — u keshda. Menyu darhol ko'rinadi. So'rov esa ketadi: ma'lumot kelganiga 3 daqiqa bo'ldi, staleTime 1 daqiqa — eskirgan. Demak fonda yangilanadi.

3. Avtomatik yangilanishlar

3.1 Uchta "qulay payt"

Eskirgan ma'lumot "birinchi qulay paytda" yangilanadi dedik. Query uchta paytni kuzatadi:

Sozlama Qachon Sukut
refetchOnMount shu kalitni ishlatadigan komponent ekranga chiqqanda true
refetchOnWindowFocus foydalanuvchi oynaga (tabga) qaytganda true
refetchOnReconnect internet qaytganda true

true — "faqat eskirgan bo'lsa". false — hech qachon. "always" — eskirgan-eskirmaganidan qat'i nazar.

Uchtasi ham staleTime ga bo'ysunadi. staleTime: 30_000 bo'lsa, 30 soniya ichida oynaga necha marta qaytsangiz ham so'rov yo'q.

3.2 Oynaga qaytish

Mehmon menyuni ochdi, Telegram'ga o'tdi, 10 daqiqadan keyin qaytdi. Bu orada narx o'zgargan bo'lishi mumkin. refetchOnWindowFocus aynan shu holat uchun.

Natija oynasida tabni almashtirish qiyin. Shuning uchun Query'ning focusManager idan foydalanamiz — u "oyna faolmi?" degan savolga javob beradi. Tugmalar foydalanuvchining ketishi va qaytishini taqlid qiladi:

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

const queryClient = new QueryClient();

let version = 0;

function fetchPrice() {
  version += 1;
  console.log("so'rov", version);
  return Promise.resolve(35000 + (version - 1) * 1000);
}

function Price() {
  const { data } = useQuery({
    queryKey: ["price", "osh"],
    queryFn: fetchPrice,
  });
  return (
    <>
      <p>Osh: {data} so'm</p>
      <button onClick={() => focusManager.setFocused(false)}>
        Telegram'ga o'tish
      </button>{" "}
      <button onClick={() => focusManager.setFocused(true)}>
        Saytga qaytish
      </button>
    </>
  );
}

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

Konsolda:

text
so'rov 1
so'rov 2

Ekranda:

text
Osh: 36000 so'm
Telegram'ga o'tish Saytga qaytish

Qaytish bilan yangi narx keldi. Haqiqiy brauzerda bu tugmalar kerak emas: Query visibilitychange hodisasini o'zi tinglaydi. «Ishga tushir» dan keyin brauzerning boshqa tabiga o'tib qayting — konsolda yangi so'rov chiqadi.

DevTools bilan ishlaganda bu xulq hayron qoldiradi: konsoldan sahifaga qaytsangiz ham Network panelida yangi so'rovlar. Bu xato emas — eskirgan ma'lumot yangilanyapti. Kerak bo'lmasa — staleTime ni oshiring yoki refetchOnWindowFocus: false.

3.3 Davriy yangilash: refetchInterval

To'rtinchi usul — vaqt bo'yicha. refetchInterval: 5000 — har 5 soniyada so'rov, ma'lumot yangi bo'lsa ham. Oshxonadagi "tayyor buyurtmalar" ekrani uchun oddiy yechim. Bu usulning nomi — polling (so'rab turish). Uning kamchiligi va muqobillari (WebSocket, SSE) — Real-time darsida.

Maslahat: staleTime ni qanday tanlash kerak? Savol bering: "foydalanuvchi necha soniyalik eski ma'lumotni ko'rsa, zarar bo'ladi?" Menyu — daqiqalar, hatto soat. Bank balansi — 0. vazifalar — 30 s (o'lchov bilan, «Vazifalar qadami» bo'limida). Bilmasangiz, sukut (0) bilan boshlang va Network panelida ortiqcha so'rovlarni ko'rganda oshiring.

4. Kalitlar: tartib va bitta joy

4.1 Kalitlar daraxti va qisman moslik

So'rov kalitlari (query key, 13-darsdagi queryKey) oddiy massiv bo'lgani bilan, ular daraxt kabi ishlaydi. ["dishes"] — hamma taomlar haqidagi so'rovlarning ildizi. ["dishes", "list", "issiq"] va ["dishes", "detail", "osh"] — uning shoxlari.

Bu nega muhim: ko'p metodlar kalitni boshidan solishtiradi. invalidateQueries({ queryKey: ["dishes"] }) — "dishes bilan boshlanadigan hamma so'rov eskirdi". Ro'yxat ham, har bir taom ham yangilanadi. Invalidatsiya (invalidation) — keshdagi yozuvni majburan "eskirgan" deb belgilash. Ekrandagilari darhol qayta so'raladi.

4.2 Kalit fabrikasi

Kalitni har komponentda qo'lda yozsangiz, bir kun ["dish", slug] va boshqa joyda ["dishes", slug] paydo bo'ladi. Ular bir-birini bilmaydi va invalidatsiya ishlamaydi.

Davosi — kalit fabrikasi (query key factory): hamma kalit bitta obyektda, funksiya sifatida:

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

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

// Hamma kalit — bitta joyda
const dishKeys = {
  all: ["dishes"],
  list: (category) => [...dishKeys.all, "list", category],
  detail: (slug) => [...dishKeys.all, "detail", slug],
};

function fakeServer(key) {
  console.log("so'rov:", JSON.stringify(key));
  return Promise.resolve(key.at(-1));
}

function List() {
  const key = dishKeys.list("issiq");
  const { data } = useQuery({
    queryKey: key,
    queryFn: () => fakeServer(key),
  });
  return <p>Ro'yxat: {data}</p>;
}

function Detail() {
  const key = dishKeys.detail("osh");
  const { data } = useQuery({
    queryKey: key,
    queryFn: () => fakeServer(key),
  });
  return <p>Taom: {data}</p>;
}

function Admin() {
  const client = useQueryClient();
  return (
    <button
      onClick={() =>
        client.invalidateQueries({ queryKey: dishKeys.all })
      }
    >
      Narxlar o'zgardi
    </button>
  );
}

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

Konsolda:

text
so'rov: ["dishes","list","issiq"]
so'rov: ["dishes","detail","osh"]
so'rov: ["dishes","list","issiq"]
so'rov: ["dishes","detail","osh"]

Ekranda:

text
Ro'yxat: issiq
Taom: osh
Narxlar o'zgardi

staleTime bir daqiqa edi — o'z-o'zidan yangilanish bo'lmasdi. Lekin bitta invalidateQueries({ queryKey: dishKeys.all }) ikkala so'rovni ham yangiladi: ikkalasining kaliti "dishes" bilan boshlanadi. Faqat ro'yxatlarni yangilash kerak bo'lsa — ["dishes", "list"].

4.3 queryOptions — kalit va funksiya birga

Fabrika kalitni bir joyga yig'di. Lekin queryFn hali har komponentda alohida. Kalit va funksiya doim juft yuradi — ["dishes", "detail", "osh"] uchun boshqa funksiya yozish xato.

queryOptions — so'rov sozlamalarini (kalit, funksiya, staleTime …) bitta obyektga yig'adigan yordamchi. JavaScript'da u obyektni o'zgartirmasdan qaytaradi. Uning kuchi — TypeScript'da: kalitga ma'lumot turi "yopishadi".

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

const queryClient = new QueryClient();

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

// Kalit + funksiya + sozlamalar — bitta joyda
function dishQuery(slug) {
  return queryOptions({
    queryKey: ["dishes", "detail", slug],
    queryFn: () => fetchDish(slug),
    staleTime: 60_000,
  });
}

function Dish() {
  const { data } = useQuery(dishQuery("osh"));
  return <p>Narx: {data?.price}</p>;
}

function Cart() {
  const client = useQueryClient();
  // Keshdan o'qish — o'sha kalit, xato qilib bo'lmaydi
  const cached = client.getQueryData(dishQuery("osh").queryKey);
  return <p>Savatda: {cached ? cached.slug : "hali yo'q"}</p>;
}

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

Konsolda:

text
so'rov: osh

Ekranda:

text
Narx: 35000
Savatda: hali yo'q

useQuery(dishQuery("osh")) — obyektni to'g'ridan-to'g'ri beramiz. getQueryData(kalit) — keshdagi qiymatni so'rovsiz oladi. Cart chizilganda javob hali kelmagan edi, shuning uchun "hali yo'q". Va Cart kesh o'zgarganini kuzatmaydi — getQueryData bir martalik o'qish, obuna emas. Kuzatish kerak bo'lsa — useQuery.

TypeScript'da farq ko'rinadi. dishQuery ning queryFn i Promise<Dish> qaytarsa, getQueryData(dishQuery("osh").queryKey) turi — Dish | undefined. queryOptions siz — unknown. Kanonda shu imkoniyat ishlatiladi. Tekshiramiz:

tsx
import { QueryClient, queryOptions } from "@tanstack/react-query";

type Dish = { slug: string; price: number };
declare function fetchDish(slug: string): Promise<Dish>;

const client = new QueryClient();

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

const a = client.getQueryData(dishQuery("osh").queryKey);
const b = client.getQueryData(["dishes", "detail", "osh"]);
console.log(a.price, b.price);

Xato:

text
TS18048: 'a' is possibly 'undefined'.
TS18046: 'b' is of type 'unknown'.

Kompilyator a ni taniydi — u Dish | undefined, faqat "keshda yo'q bo'lishi mumkin" deb ogohlantiradi (a?.price bilan hal bo'ladi). b haqida esa hech narsa bilmaydi — unknown, price maydoni bor-yo'qligini ham.

queryOptions qaytargan obyekt hamma joyda ishlaydi: useQuery, useSuspenseQuery, prefetchQuery, fetchQuery, router loader. Ularni TanStack Query V darsida ko'ramiz. vazifalar ning royxatSorovi() — aynan shunday funksiya.

Tekshirib ko'ring: Kalitlar: ["dishes", "list", "issiq"], ["dishes", "detail", "osh"], ["drinks", "list"]. invalidateQueries({ queryKey: ["dishes", "list"] }) qaysilarini yangilaydi?

Javob

Faqat birinchisini. Solishtirish kalit boshidan: ["dishes", "list", "issiq"] — "dishes", "list" bilan boshlanadi. ["dishes", "detail", "osh"] ning ikkinchi elementi "detail", mos emas. ["drinks", "list"] — birinchi elementdayoq farq.

5. So'rovni boshqarish: enabled va select

5.1 enabled — hozircha so'rama

Qidiruv maydoni: mehmon "o" yozdi. Bitta harf bo'yicha qidirish ma'nosiz — yuzta natija. Ikki harfdan keyin qidirish kerak. enabled: false — so'rov ketmaydi, Query kutib turadi:

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

const queryClient = new QueryClient();

const MENU = ["Osh", "Oshqovoq somsa", "Manti", "Lag'mon"];

function searchMenu(text) {
  console.log("qidiruv:", text);
  return new Promise((resolve) =>
    setTimeout(
      () =>
        resolve(
          MENU.filter((d) =>
            d.toLowerCase().startsWith(text.toLowerCase()),
          ),
        ),
      200,
    ),
  );
}

function Search() {
  const [text, setText] = useState("");
  const query = useQuery({
    queryKey: ["search", text],
    queryFn: () => searchMenu(text),
    enabled: text.length >= 2,
  });
  console.log(query.status, query.fetchStatus);
  let result;
  if (text.length < 2) result = "Kamida 2 harf yozing";
  else if (query.isLoading) result = "Qidirilmoqda…";
  else result = query.data.join(", ");
  return (
    <>
      <input
        aria-label="Qidiruv"
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <p>{result}</p>
    </>
  );
}

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

Konsolda:

text
pending idle
pending idle
pending fetching
qidiruv: osh
success idle

Ekranda ("osh" yozilgach):

text
Osh, Oshqovoq somsa

Konsolni qatorma-qator o'qiymiz. Birinchi qator — maydon bo'sh, ikkinchisi — "o" yozildi. Ikkalasida ham so'rov yo'q. "osh" yozilganda enabled rost bo'ldi va so'rov ketdi. Oraliq nuqtada ekranda "Qidirilmoqda…" edi, keyin natija.

Birinchi ikki qatorga yana qarang: pending idle. So'rov o'chirilgan paytda status — "pending" (ma'lumot yo'q), fetchStatus — "idle" (so'rov ketmayapti). Bu tuzoq: isPending ga qarab "Yuklanmoqda…" ko'rsatsangiz, foydalanuvchi bitta harf yozgach abadiy aylanuvchi belgini ko'radi. Shuning uchun kodda isLoading — u isPending && isFetching, ya'ni "ma'lumot yo'q va so'rov ketyapti".

enabled ning ikki ko'p uchraydigan ishi:

  • foydalanuvchi kiritishi kutilyapti — qidiruv, filtr, forma;
  • boshqa so'rov kutilyapti — bog'liq so'rovlar (pastda).

TypeScript'da enabled o'rniga skipToken ham ishlatiladi: queryFn: text.length >= 2 ? () => searchMenu(text) : skipToken. Afzalligi — funksiya ichida text turini toraytirish oson.

5.2 select — keshdan kerakli qismini olish

Bosh sahifada "Bugun 4 ta taom" yozuvi kerak. Butun menyu keshda bor. Alohida "sanash" so'rovi yozish shart emas — select keshdagi ma'lumotni komponentga berishdan oldin o'zgartiradi:

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

const queryClient = new QueryClient();

function fetchMenu() {
  console.log("so'rov ketdi");
  return Promise.resolve([
    { name: "Osh", price: 35000 },
    { name: "Lag'mon", price: 28000 },
    { name: "Manti", price: 30000 },
    { name: "Ko'k choy", price: 5000 },
  ]);
}

const menuQuery = { queryKey: ["menu"], queryFn: fetchMenu };

function Count() {
  const { data } = useQuery({
    ...menuQuery,
    select: (menu) => menu.length,
  });
  return <p>Bugun {data} ta taom</p>;
}

function Cheapest() {
  const { data } = useQuery({
    ...menuQuery,
    select: (menu) =>
      menu.reduce((a, b) => (b.price < a.price ? b : a)).name,
  });
  return <p>Eng arzoni: {data}</p>;
}

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

Konsolda:

text
so'rov ketdi

Ekranda:

text
Bugun 4 ta taom
Eng arzoni: Ko'k choy

Kesh bitta, so'rov bitta, har komponent o'z "ko'rinishini" oladi. Keshda esa asl menyu qoladi — select uni o'zgartirmaydi.

Ikkinchi foyda — kamroq render. Count faqat select natijasi (son) o'zgarganda qayta chiziladi. Menyuda narx o'zgarsa, son o'sha — Count jim turadi. Bu memo, useMemo, useCallback darsidagi g'oyaning Query'dagi ko'rinishi.

Diqqat: select ga har render'da yangi funksiya bersangiz (select: (m) => … komponent ichida), Query uni har safar qayta ishlatadi. Oddiy hisobda (length) bu sezilmaydi. Og'ir hisobda funksiyani komponentdan tashqariga chiqaring yoki useCallback bilan o'rang.

6. Bir nechta so'rov

6.1 Parallel so'rovlar

Bitta komponentda ikkita useQuery — ular parallel ketadi, biri ikkinchisini kutmaydi:

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

const queryClient = new QueryClient();
const start = Date.now();

function fakeServer(name, value) {
  console.log("boshlandi:", name);
  return new Promise((resolve) =>
    setTimeout(() => resolve(value), 200),
  );
}

function Dashboard() {
  const menu = useQuery({
    queryKey: ["menu"],
    queryFn: () => fakeServer("menyu", 4),
  });
  const orders = useQuery({
    queryKey: ["orders"],
    queryFn: () => fakeServer("buyurtmalar", 12),
  });
  if (menu.isPending || orders.isPending) {
    return <p>Yuklanmoqda…</p>;
  }
  const fast = Date.now() - start < 350;
  return (
    <p>
      {menu.data} ta taom, {orders.data} ta buyurtma
      {fast ? " (parallel)" : " (ketma-ket)"}
    </p>
  );
}

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

Konsolda:

text
boshlandi: menyu
boshlandi: buyurtmalar

Ekranda:

text
4 ta taom, 12 ta buyurtma (parallel)

Har biri 200 ms. Ketma-ket bo'lsa — 400 ms, parallel — 200 ms. Ketma-ket va parallel bajarish darsidagi Promise.all ning React'dagi o'xshashi.

6.2 Bog'liq so'rovlar

Ba'zan ikkinchi so'rov birinchisining natijasiga muhtoj. Mehmonni telefon raqami bo'yicha topamiz, keyin uning buyurtmalarini olamiz — buyurtmalar uchun mehmonning id si kerak. Bu bog'liq so'rov (dependent query): ikkinchisi enabled bilan birinchisini kutadi:

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

const queryClient = new QueryClient();

function fetchGuest(phone) {
  console.log("mehmon so'raldi:", phone);
  return new Promise((resolve) =>
    setTimeout(() => resolve({ id: 7, name: "Malika" }), 200),
  );
}

function fetchOrders(guestId) {
  console.log("buyurtmalar so'raldi:", guestId);
  return new Promise((resolve) =>
    setTimeout(() => resolve(["Osh", "Ko'k choy"]), 200),
  );
}

function GuestOrders({ phone }) {
  const guest = useQuery({
    queryKey: ["guest", phone],
    queryFn: () => fetchGuest(phone),
  });
  const guestId = guest.data?.id;
  const orders = useQuery({
    queryKey: ["orders", guestId],
    queryFn: () => fetchOrders(guestId),
    enabled: guestId !== undefined,
  });
  if (guest.isPending) return <p>Mehmon qidirilmoqda…</p>;
  if (orders.isPending) return <p>Buyurtmalar yuklanmoqda…</p>;
  return (
    <p>
      {guest.data.name}: {orders.data.join(", ")}
    </p>
  );
}

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <GuestOrders phone="+998 90 000 00 00" />
    </QueryClientProvider>
  );
}

Konsolda:

text
mehmon so'raldi: +998 90 000 00 00
buyurtmalar so'raldi: 7

Ekranda:

text
Malika: Osh, Ko'k choy

Ishlaydi, lekin narxi bor: 200 + 200 = 400 ms. Bu sharshara (waterfall) — so'rovlar zinapoya kabi birin-ketin. Har bog'liq so'rov kutishni uzaytiradi. Iloji bo'lsa, server bilan kelishing: bitta so'rov ikkalasini qaytarsin (/guests/by-phone/…?include=orders). Iloji bo'lmasa — bog'liq so'rov to'g'ri yechim, faqat uni ongli ishlating.

6.3 useQueries — so'rovlar soni oldindan noma'lum

Ofitsiant uchta stolga xizmat qiladi: 2, 4 va 7. Ertaga — beshta. Har stolga useQuery yozib bo'lmaydi: Hooklar qoidalari — hook siklda chaqirilmaydi. useQueries — massivdagi har element uchun bitta so'rov:

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

const queryClient = new QueryClient();

const TOTALS = { 2: 63000, 4: 40000, 7: 30000 };

function fetchTotal(table) {
  console.log("stol:", table);
  return new Promise((resolve) =>
    setTimeout(() => resolve(TOTALS[table]), 200),
  );
}

function Waiter({ tables }) {
  const result = useQueries({
    queries: tables.map((table) => ({
      queryKey: ["total", table],
      queryFn: () => fetchTotal(table),
    })),
    combine: (results) => ({
      pending: results.some((r) => r.isPending),
      sum: results.reduce((s, r) => s + (r.data ?? 0), 0),
    }),
  });
  if (result.pending) return <p>Hisoblanmoqda…</p>;
  return (
    <p>
      {tables.length} ta stol, jami: {result.sum} so'm
    </p>
  );
}

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Waiter tables={[2, 4, 7]} />
    </QueryClientProvider>
  );
}

Konsolda:

text
stol: 2
stol: 4
stol: 7

Ekranda:

text
3 ta stol, jami: 133000 so'm

queries — har biri oddiy useQuery sozlamasi. Uchala so'rov parallel. combine — natijalar massivini bitta qiymatga yig'adi (bu yerda — "kutilyaptimi?" va yig'indi). combine siz useQueries natijalar massivini qaytaradi. Har stolning kaliti alohida (["total", 4]), shuning uchun boshqa komponent faqat 4-stolni so'rasa — keshdan oladi.

Tekshirib ko'ring: Bog'liq so'rovlar misolida enabled: guestId !== undefined ni olib tashlasak nima bo'ladi?

Javob

Ikkinchi so'rov darhol, mehmon topilmasdan ketadi: fetchOrders(undefined). Kalit — ["orders", undefined]. Haqiqiy serverda bu /orders?guest=undefined kabi noto'g'ri so'rov va xato bo'lardi. Mehmon topilgach kalit ["orders", 7] ga o'zgaradi va yana so'rov ketadi. Natijada bitta keraksiz, xato so'rov.

7. Vazifalar qadami: kesh qoidalari va o'lchov (holat 14 davomi)

7.1 royxatSorovi — so'rov bitta joyda

Oldingi darsda useVazifalar ichida royxatSorovi(mijoz) ni ko'rdik. Mana u (kanon, izohlar bo'lingan):

ts
/**
 * Ro'yxat so'rovi — bitta joyda (hook, loader, prefetch shuni
 * oladi). initialData — localStorage'dagi nusxa: sahifa birinchi
 * kadrdayoq ro'yxat bilan (CLS 0, 13/#30), initialDataUpdatedAt
 * 0 — u "eski": Query darhol serverdan yangisini so'raydi
 */
export function royxatSorovi(mijoz: QueryClient = sorovMijozi) {
  return queryOptions({
    queryKey: royxatKaliti,
    queryFn: ({ signal }) => royxatniYukla(mijoz, signal),
    initialData: () => yukla() ?? boshlangichVazifalar,
    initialDataUpdatedAt: 0,
    // Internet qaytdi — eskirgan-eskirmaganidan qat'i nazar
    // solishtirish (v8 dagi "online" hodisasi)
    refetchOnReconnect: "always",
  });
}

Bugungi darsdan to'rt narsa:

  • queryOptions — kalit va funksiya juft. useVazifalar ham, vazifa sahifasining loader'i ham shuni oladi.
  • initialData — so'rovgacha keshga qo'yiladigan boshlang'ich qiymat. Bu yerda — localStorage dagi nusxa. Natija: birinchi kadrdayoq ro'yxat, "Yuklanmoqda…" yo'q (13-qismdagi CLS 0 o'lchovi saqlanadi). Funksiya ko'rinishida — faqat kerak bo'lganda bir marta o'qiladi.
  • initialDataUpdatedAt: 0 — "bu ma'lumot 1970-yil 1-yanvarda kelgan". Ya'ni u albatta eskirgan va Query darhol serverdan yangisini so'raydi. Bu qatorsiz staleTime 30 s bo'lgani uchun localStorage'dagi nusxa 30 s "yangi" hisoblanardi — server bilan solishtirish kechikardi.
  • refetchOnReconnect: "always" — internet qaytganda, ma'lumot yangi bo'lsa ham. 17-qismdagi online hodisasi shu.

7.2 E2e topgan xato: oflayn ochilgan sahifa

Brauzer tekshiruvi (e2e) bitta haqiqiy xatoni topdi. Sahifa internetsiz ochilsa (Service Worker keshidan), so'rov yiqilib "xato" bo'lardi. Internet qaytganda esa qayta so'ralmasdi. Sababi — Query'ning onlineManager i sahifa ochilganda internetni bor deb hisoblaydi va faqat online/offline hodisalarini tinglaydi. Sahifa oflayn ochilsa, offline hodisasi bo'lmaydi — Query bilmaydi. Tuzatish — bitta qator (kanon):

ts
// Query v5 sahifa ochilganda internetni BOR deb hisoblaydi va
// faqat online/offline hodisalarini tinglaydi. Sahifa internetsiz
// ochilsa (SW keshidan) — so'rov yiqilib "xato" bo'lardi va
// internet qaytganda "online" hodisasi holatni o'zgartirmagani
// uchun qayta so'ralmasdi (e2e topdi: 13/#22 "onlayn: sinxron
// qaytdi"). Boshlang'ich holat — brauzerdan: oflayn bo'lsa
// so'rov KUTADI va internet qaytishi bilan ketadi
onlineManager.setOnline(navigator.onLine);

Oldingi darsdagi paused holati endi to'g'ri ishlaydi: oflayn ochilgan sahifada so'rov kutadi, internet qaytishi bilan ketadi.

7.3 Loader — o'sha kesh

Vazifa sahifasining loader'i (data mode darsida yozgan edik) ham endi Query keshidan oladi. fetchQuery — useQuery ning komponentsiz ko'rinishi: kesh yangi bo'lsa — so'rovsiz qaytaradi, eskirgan bo'lsa — serverdan oladi va keshga yozadi (kanon):

diff
+// 19/#14: ro'yxat — Query keshidan (ro'yxat sahifasi bilan
+// BITTA kesh). fetchQuery: kesh yangi (30 s) bo'lsa —
+// so'rovsiz, eskirgan bo'lsa — serverdan. Server javob bermasa
+// (oflayn, xato) — keshdagi nusxa. Bekor qilingan so'rov —
+// qayta tashlanadi (eskirgan navigatsiya)
 async function royxatniOl(signal: AbortSignal): Promise<Vazifalar> {
+  const kesh = (): Vazifalar =>
+    sorovMijozi.getQueryData<Vazifalar>(royxatKaliti) ??
+    yukla() ??
+    boshlangichVazifalar;
   if (!navigator.onLine) {
-    return keshdagi();
+    return kesh();
   }
   try {
-    const javob = await api.royxat({ signal });
-    return javob.versiya === 0 ? keshdagi() : javob.vazifalar;
+    return await sorovMijozi.fetchQuery(royxatSorovi());

Loader komponent emas, hook chaqira olmaydi. Shuning uchun u sorovMijozi ni to'g'ridan-to'g'ri import qiladi — QueryClient modul darajasida yaratilgani shu uchun qulay. Testda esa har testdan oldin sorovMijozi.clear() — kesh tozalanadi, testlar bir-birining ma'lumotini ko'rmaydi.

7.4 O'lchov: 7 qadamda nechta so'rov

Server state vs client state darsidagi o'lchov skripti (olchov/sorovlar.mjs, Chrome 155) o'sha 7 qadamni uch variantda bajardi: Query'gacha (o'sha darsdagi holat), Query + staleTime: 0 (sukut) va Query + staleTime: 30_000 (kanon):

Qadam Query'gacha staleTime 0 30 s
1. ro'yxat ochildi GET GET GET
2. → Statistika — GET —
3. → Ro'yxat GET GET —
4. → «Batafsil» GET GET —
5. Orqaga → Ro'yxat GET GET —
6. serverda +1; → Statistika — (3) GET (4) — (3)
7. Statistika, F5 — (3) GET (4) GET (4)

Qavsda — statistikadagi "Jami". Serverda 6-qadamdan keyin 4 ta vazifa bor.

7 qadamda serverga GET so'rovlari
  • Query'gachastatistika eskiradi: 34 ta
  • Query, staleTime 0har o'tishda7 ta
  • Query, staleTime 30 s (kanon)F5 dan keyin — 42 ta

Manba: sinov/19/natijalar/11-sorovlar-oldin.txt, 14-sorovlar-stale0.txt, 14-sorovlar-keyin.txt (Chrome 155.0.8059.40)

Uch variantni solishtiramiz:

  • Query'gacha — 4 ta so'rov va xato: statistika hatto F5 dan keyin ham 3 ko'rsatadi.
  • staleTime: 0 — xato yo'q, boshqa qurilmadagi +1 darhol ko'rinadi (6-qadam). Narxi — har o'tishda so'rov, 7 tadan 7.
  • 30 s (kanon) — 2 ta so'rov. Narxi — boshqa qurilmadagi o'zgarish 30 s gacha kechikishi mumkin (6-qadam: 3). F5 yoki 30 soniyadan keyin — to'g'ri raqam.

Kurs 30 s ni tanladi: vazifalar — bitta odamning ro'yxati, boshqa qurilmadan yarim daqiqalik kechikish zararsiz. Bir nechta odam bir ro'yxatda ishlasa — staleTime kamroq yoki real-time (Real-time darsida kechikishni 254 ms ga tushiramiz).

Brauzer tekshiruvlari (holat 14) — o'lchov xulosasini doimiy tekshiruvga aylantirdi:

text
--- 19/#13–#14 server holati (TanStack Query)
✅ ochilish: bitta GET: ["GET"]
✅ statistika → ro'yxat → vazifa (30 s ichida): serverga so'rov yo'q (kesh): []
✅ statistika (sahifa yangilandi): keshdan darhol, keyin serverdan — 4: ["4","3","1 (25%)"]
✅ server holati: CSP buzilishi yo'q: []

Commit hali yo'q — yozish (useMutation) keyingi darsda.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Network'da tabga qaytganda so'rovlar staleTime: 0 + refetchOnWindowFocus staleTime ni oshiring
staleTime katta, lekin "Yuklanmoqda…" qaytadi gcTime < staleTime, yozuv o'chdi gcTime ≥ staleTime
invalidateQueries hech narsani yangilamadi kalit boshqacha yozilgan ("dish" / "dishes") kalit fabrikasi yoki queryOptions
enabled: false da abadiy "Yuklanmoqda…" isPending ishlatilgan isLoading (isPending && isFetching)
getQueryData dan keyin ekran yangilanmaydi bir martalik o'qish, obuna emas useQuery (yoki select)
ikkinchi so'rov undefined bilan ketdi bog'liq so'rovda enabled yo'q enabled: id !== undefined
useQuery siklda — React xatosi hooklar qoidasi useQueries
oflayn ochilgan sahifa internet qaytganda yangilanmaydi onlineManager boshlang'ichda "bor" onlineManager.setOnline(navigator.onLine)

Oxirgisi — umumiy tuzoq emas, PWA va Service Worker'li ilovalarga xos. Lekin aynan shunday xatolar foydalanuvchida bir marta uchrab, "sayt ishlamayapti" degan taassurot qoldiradi.

9. Mashqlar

Mashqlar kurs/mashqlar/19/14-kesh/ da.

1-mashq (oson): so'rov ketadimi?

staleTime: 10_000, gcTime: 60_000. Ma'lumot 0-soniyada keldi. Har holat uchun yozing: so'rov ketadimi ("ha"/"yo'q") va "Yuklanmoqda…" bo'ladimi.

  • 5-soniyada boshqa komponent shu kalitni ochdi — so'rov:
  • 15-soniyada foydalanuvchi tabga qaytdi — so'rov:
  • Komponent 20-soniyada yopildi, 50-soniyada qayta ochildi — "Yuklanmoqda…":
  • Komponent 20-soniyada yopildi, 90-soniyada qayta ochildi — "Yuklanmoqda…":
Yechim
  1. Yo'q — 5 < 10, ma'lumot yangi.
  2. Ha — 15 > 10, eskirgan, refetchOnWindowFocus fonda yangilaydi. Ekranda eski ma'lumot qoladi.
  3. Yo'q — faolsiz bo'lganiga 30 s, gcTime 60 s, yozuv keshda. Darhol ko'rinadi (va eskirgani uchun fonda yangilanadi).
  4. Ha — faolsiz bo'lganiga 70 s > 60 s, yozuv o'chgan. Boshidan yuklanadi.

2-mashq (o'rta): kalit fabrikasi

Kod ishlaydi, lekin kalitlar qo'lda yozilgan va «Yangilash» faqat ro'yxatni yangilaydi — taom sahifasi eski qoladi. orderKeys fabrikasini yozing (all, list(), detail(id)), uchala joyda ishlating va «Yangilash» orderKeys.all ni invalidatsiya qilsin.

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

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

function fakeServer(key) {
  console.log("so'rov:", key.join("/"));
  return Promise.resolve(key.length);
}

function List() {
  const key = ["orders", "list"];
  const { data } = useQuery({
    queryKey: key,
    queryFn: () => fakeServer(key),
  });
  return <p>Ro'yxat: {data}</p>;
}

function Detail() {
  const key = ["order", 12];
  const { data } = useQuery({
    queryKey: key,
    queryFn: () => fakeServer(key),
  });
  return <p>Buyurtma: {data}</p>;
}

function Refresh() {
  const client = useQueryClient();
  return (
    <button
      onClick={() =>
        client.invalidateQueries({ queryKey: ["orders", "list"] })
      }
    >
      Yangilash
    </button>
  );
}

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

Konsolda:

text
so'rov: orders/list
so'rov: order/12
so'rov: orders/list
Yechim
jsx
import {
  QueryClient,
  QueryClientProvider,
  useQuery,
  useQueryClient,
} from "@tanstack/react-query";

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

const orderKeys = {
  all: ["orders"],
  list: () => [...orderKeys.all, "list"],
  detail: (id) => [...orderKeys.all, "detail", id],
};

function fakeServer(key) {
  console.log("so'rov:", key.join("/"));
  return Promise.resolve(key.length);
}

function List() {
  const key = orderKeys.list();
  const { data } = useQuery({
    queryKey: key,
    queryFn: () => fakeServer(key),
  });
  return <p>Ro'yxat: {data}</p>;
}

function Detail() {
  const key = orderKeys.detail(12);
  const { data } = useQuery({
    queryKey: key,
    queryFn: () => fakeServer(key),
  });
  return <p>Buyurtma: {data}</p>;
}

function Refresh() {
  const client = useQueryClient();
  return (
    <button
      onClick={() =>
        client.invalidateQueries({ queryKey: orderKeys.all })
      }
    >
      Yangilash
    </button>
  );
}

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

Konsolda:

text
so'rov: orders/list
so'rov: orders/detail/12
so'rov: orders/list
so'rov: orders/detail/12

Asl kodda ikki xato bor edi. Birinchisi — ["order", 12] ning boshi "order", ro'yxatniki "orders" — bitta harf farq va ular boshqa "daraxt"da. Ikkinchisi — invalidatsiya faqat ro'yxat kalitiga. Fabrika ikkalasini bir yo'la tuzatdi: hamma kalit orderKeys.all dan o'sadi.

3-mashq (qiyin): bog'liq so'rov va select

«Bahor» ofitsianti stol raqamini biladi. Kerak:

  1. fetchTable(4) → { table: 4, orderId: 31 } (stoldagi joriy buyurtma raqami).
  2. fetchOrder(31) → [{ name: "Osh", price: 35000 }, { name: "Ko'k choy", price: 5000 }] — faqat 1-so'rov tugagach (enabled).
  3. Buyurtma so'rovida select bilan jami summa hisoblansin — komponent faqat sonni oladi.
  4. Ekranda: "4-stol: 40000 so'm". Yuklanayotganda — "Yuklanmoqda…".

Ikkala soxta server 200 ms kutsin. Ishora: «Bog'liq so'rovlar» bo'limi va reduce (reduce).

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

const queryClient = new QueryClient();

function delay(value) {
  return new Promise((resolve) =>
    setTimeout(() => resolve(value), 200),
  );
}

function fetchTable(table) {
  console.log("stol:", table);
  return delay({ table, orderId: 31 });
}

function fetchOrder(orderId) {
  console.log("buyurtma:", orderId);
  return delay([
    { name: "Osh", price: 35000 },
    { name: "Ko'k choy", price: 5000 },
  ]);
}

const sumPrices = (items) =>
  items.reduce((sum, item) => sum + item.price, 0);

function TableTotal({ table }) {
  const tableQuery = useQuery({
    queryKey: ["table", table],
    queryFn: () => fetchTable(table),
  });
  const orderId = tableQuery.data?.orderId;
  const total = useQuery({
    queryKey: ["order", orderId],
    queryFn: () => fetchOrder(orderId),
    enabled: orderId !== undefined,
    select: sumPrices,
  });
  if (tableQuery.isPending || total.isPending) {
    return <p>Yuklanmoqda…</p>;
  }
  return (
    <p>
      {table}-stol: {total.data} so'm
    </p>
  );
}

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

Konsolda:

text
stol: 4
buyurtma: 31

Ekranda:

text
4-stol: 40000 so'm

sumPrices komponentdan tashqarida — har render'da yangi funksiya yaratilmaydi («select» bo'limidagi ogohlantirish). Keshda esa buyurtmaning to'liq ro'yxati qoladi: boshqa komponent taomlar nomini ko'rsatishi kerak bo'lsa, so'rovsiz oladi.

4-mashq: Vazifalar qadami

refactor/tanstack-query branch'ida davom eting: royxatSorovi (queryOptions, initialData, initialDataUpdatedAt: 0, refetchOnReconnect: "always"), onlineManager.setOnline(navigator.onLine), loader'dagi fetchQuery, VazifaSahifasi.test.tsx dagi sorovMijozi.clear().

Yechim

Tekshiruv ro'yxati:

  • npm run dev, DevTools Network (Fetch/XHR filtri). Ro'yxat → Statistika → Ro'yxat → «Batafsil» → Orqaga: 30 s ichida faqat birinchi GET.
  • 30 soniya kuting va tabga qayting — bitta GET (eskirgan, refetchOnWindowFocus).
  • Network → Offline, sahifani yangilang: ro'yxat localStorage'dan, "oflayn". Online qiling — so'rov o'zi ketadi (onlineManager qatorisiz ketmasdi).
  • npm test — VazifaSahifasi testlari sorovMijozi.clear() siz bir-biriga ta'sir qiladi.

10. Real ishda

  • staleTime — eng ko'p so'raladigan sozlama. Ko'p jamoalar defaultOptions da 30–60 s qo'yadi va faqat "jonli" ma'lumotda kamaytiradi. Sukutdagi 0 — xavfsiz, lekin "nega shuncha so'rov?" degan savolga sabab.
  • Kalit fabrikasi — katta loyihalarda odatiy naqsh: api/dishes/keys.ts. TanStack jamoasi ham buni tavsiya qiladi. Hozirgi uslub — queryOptions funksiyalari (dishQueries.detail(slug)), kalit va funksiya birga.
  • select — "bitta API javobi, ko'p ko'rinish" holatida: ro'yxat, son, filtrlangan qism. Alohida endpoint yozdirishdan oldin select bilan yetishini tekshiring.
  • Sharshara — sekin sahifalarning eng ko'p uchraydigan sababi. Lighthouse va Network panelidagi "zinapoya" — shu belgi.
  • Intervyu savollari: "staleTime va gcTime farqi?", "nega tabga qaytganda so'rov ketadi?", "invalidateQueries qanday ishlaydi?", "bog'liq so'rov va uning narxi?".

Xulosa

  • staleTime — ma'lumot qancha yangi turadi (sukut 0). gcTime — faolsiz yozuv qancha saqlanadi (sukut 5 daq). gcTime ≥ staleTime.
  • Eskirgan ma'lumot uch paytda fonda yangilanadi: komponent chiqqanda, oynaga qaytilganda, internet qaytganda. Ekrandagi ma'lumot yo'qolmaydi.
  • Kalitlar — daraxt: invalidateQueries boshidan solishtiradi. Kalitlarni fabrikaga yoki queryOptions funksiyalariga yig'ing.
  • enabled — so'rovni kutdiradi (isLoading bilan ko'rsating), select — keshdan kerakli qismni oladi.
  • Bir nechta useQuery — parallel; bog'liq so'rov — sharshara; soni noma'lum — useQueries.

Keyingi dars: TanStack Query III: mutatsiyalar va keshni yangilash — useMutation, invalidateQueries va setQueryData, optimistik yangilash va qaytarish; vazifalar dagi holat 14 ning commit'i.

Manbalar

  • TanStack Query v5: Important Defaults — tanstack.com/query/v5/docs/framework/react/guides/important-defaults (2026-10, v5.104)
  • TanStack Query v5: Caching Examples, Window Focus Refetching, Query Invalidation — …/guides/caching, …/guides/window-focus-refetching, …/guides/query-invalidation
  • TanStack Query v5: Query Keys, Query Options, Dependent Queries, Parallel Queries — …/guides/query-keys, …/guides/query-options, …/guides/dependent-queries, …/guides/parallel-queries
  • TkDodo: Effective React Query Keys — tkdodo.eu/blog/effective-react-query-keys
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
TanStack Query kesh: staleTime, gcTime, kalitlar va bog'liq so'rovlar — IlmHamroh