IlmHamroh
JavaScript Full-stack/18-qism. React chuqur3/42-dars28 daqiqa
Mundarija (33)

Suspense: kutish holatini deklarativ ko'rsatish — fallback, ichma-ich chegaralar va transition

Qisqacha: <Suspense fallback={…}> — "ichimdagi komponentlar tayyor bo'lmaguncha, ularning o'rnida fallback ni ko'rsat" degan chegara. Komponent use(promise) yoki lazy bilan to'xtaydi (suspend), React esa eng yaqin chegarani topib, uning fallback'ini chizadi. Promise tayyor bo'lgach — haqiqiy kontent. "Yuklanmoqda…" endi har komponentdagi if (status === "loading") emas, daraxtdagi bitta joy. Chegara qayerda tursa, foydalanuvchi shuni ko'radi: bitta katta chegara — hammasi birga, ichma-ich chegaralar — bo'lak-bo'lak. Allaqachon ko'rinib turgan chegara ichidagi yangilanishni startTransition ga o'rasangiz, React fallback o'rniga eski ekranni qoldiradi.

Bu darsda

  • <Suspense fallback> ni yozasiz va kutish holatini jonli ko'rasiz: avval fallback, keyin kontent.
  • Nima suspend qiladi (use, lazy, framework ma'lumoti) va nima qilmaydi (useEffect dagi fetch) — farqini bilasiz.
  • Ichma-ich chegaralar bilan sahifani bo'lak-bo'lak ochasiz va chegara joyini UX bo'yicha tanlaysiz.
  • Transition bilan fallback "miltillashini" yo'qotasiz va nega chegara oldindan turishi kerakligini tushuntirasiz.
  • Skeleton yasaysiz va vazifalar da Suspense qayerda kerak, qayerda kerak emasligini o'lchov bilan asoslaysiz.

Oldin bilishingiz kerak: useTransition, useDeferredValue, Effektda ma'lumot olish, Promise asoslari.

1. Nega bu kerak?

Effektda ma'lumot olish darsida bitta taomni ko'rsatish uchun 30 qatorga yaqin kod yozdik: uchta state, effekt, abort va uchta return. Endi Jasur aka menyu sahifasini xohlaydi: tepada menyu, pastda mehmonlar sharhlari, yonida bugungi narxlar. Uchalasi ham serverdan keladi.

Sardor har komponentga o'z "Yuklanmoqda…" sini yozdi. Natija — "popkorn" sahifa: uchta yozuv har xil paytda paydo bo'lib, har xil paytda yo'qoladi. Sahifa sakraydi, ko'z qayerga qarashni bilmaydi. Jasur aka so'radi: "Menyu va narx birga chiqsin, sharhlar keyin kelsa ham mayli. Shuni qila olasizmi?" Har komponent o'z holatini o'zi boshqarsa — buni sozlash qiyin.

Hayotiy o'xshatish: teatr pardasi. Sahna tayyor bo'lmaguncha parda yopiq turadi — tomoshabin dekoratsiya ko'chirilayotganini ko'rmaydi. Aktyorlarning hammasi joyida bo'lgach, parda ochiladi. Katta spektaklda har sahna qismining o'z pardasi bo'lishi mumkin. Suspense — ana shu parda. Qayerga osilishini siz tanlaysiz, ochilish vaqtini React kuzatadi.

2. Birinchi Suspense

2.1 Komponent "to'xtaydi"

Komponentni kutishga majbur qilish uchun bizga use kerak. Uni Hooklar qoidalari va Context I darslarida Context'ni o'qish uchun ishlatgansiz. use Promise'ni ham qabul qiladi. Promise tayyor bo'lsa — uning qiymatini qaytaradi. Tayyor bo'lmasa — komponentni shu qatorda to'xtatadi. To'liq ichki ishlashi — keyingi darsda, bugun faqat shu qoida yetarli.

Promise'ni komponentdan tashqarida yaratamiz — modul yuklanganda bir marta. Nega bu muhim — keyingi darsning asosiy mavzusi. Hozircha fetch o'rnida 500 ms dan keyin menyu beradigan Promise:

jsx
import { Suspense, use, useEffect } from "react";

// fetch o'rnida: menyu 500 ms dan keyin keladi
const menuPromise = new Promise((resolve) => {
  setTimeout(() => resolve(["Osh", "Lag'mon", "Manti"]), 500);
});

function Menu() {
  console.log("Menu: chizishga urinish");
  const dishes = use(menuPromise); // tayyor bo'lmasa — to'xtaydi
  console.log("Menu: ma'lumot keldi");
  return <p>{dishes.join(", ")}</p>;
}

function Loading() {
  useEffect(() => console.log("fallback ekranga chiqdi"), []);
  return <p>Menyu yuklanmoqda…</p>;
}

export default function App() {
  return (
    <section>
      <h2>«Bahor» menyusi</h2>
      <Suspense fallback={<Loading />}>
        <Menu />
      </Suspense>
    </section>
  );
}

Konsolda (100 ms da):

text
Menu: chizishga urinish
fallback ekranga chiqdi
Menu: chizishga urinish

Ekranda (100 ms da):

text
«Bahor» menyusi
Menyu yuklanmoqda…

Endi o'sha kodni o'zgartirmasdan 700 ms kutamiz:

jsx
// yuqoridagi kod — o'zgarishsiz, faqat kutish vaqti boshqa

Konsolda (700 ms da):

text
Menu: chizishga urinish
fallback ekranga chiqdi
Menu: chizishga urinish
Menu: chizishga urinish
Menu: ma'lumot keldi

Ekranda (700 ms da):

text
«Bahor» menyusi
Osh, Lag'mon, Manti

«Ishga tushir» ni bosing: yarim soniya "Menyu yuklanmoqda…", keyin menyu. Konsolga sekin qarang:

  1. Menu chizila boshladi, use qatorida to'xtadi. Ikkinchi console.log gacha yetib bormadi.
  2. React eng yaqin <Suspense> ni topdi va fallback ni ekranga chiqardi. Loading effekti buni tasdiqladi.
  3. Yana bir Menu: chizishga urinish. Bu React 19 ning prewarm ("oldindan isitish") odati. Fallback ko'ringach, React to'xtagan qismni fonda yana bir bor chizib ko'radi. Maqsad — daraxtning chuqurroq joyidagi boshqa so'rov va lazy kodlar ham erta boshlansin.
  4. 500 ms da Promise hal bo'ldi. React Menu ni qaytadan chizdi — bu safar use qiymat qaytardi.

Menu ning ichida isLoading ham, useEffect ham, useState ham yo'q. U faqat tayyor ma'lumotni chizadi. Kutish holati butunlay yuqoriga — chegaraga ko'chdi.

Vaqt Menu Ekranda
0 ms use da to'xtadi fallback: "Menyu yuklanmoqda…"
0 ms dan keyin prewarm: yana to'xtadi fallback
500 ms Promise hal bo'ldi, qayta chizildi "Osh, Lag'mon, Manti"

2.2 To'xtash qanday ishlaydi

Suspend (to'xtash) — komponent render paytida "men hali tayyor emasman" deb React'ga bildiradi. React bu komponentni chala holda ekranga qo'ymaydi. U daraxt bo'ylab yuqoriga chiqib, eng yaqin <Suspense> ni qidiradi. Chegara (boundary) — shu <Suspense>: uning ichidagi hamma narsa o'rniga fallback chiziladi.

Diagrammada «ha» yo'li — oddiy render, «yo'q» yo'li — to'xtash. Halqaga qarang: Promise hal bo'lgach, React o'sha komponentni boshidan qayta chizadi.

flowchart TD
  A["React Menu ni chizadi"] --> B{"use: Promise tayyormi?"}
  B -- "ha" --> C["Qiymat qaytadi, kontent chiziladi"]
  B -- "yo'q" --> D["Menu to'xtaydi (suspend)"]
  D --> E["Eng yaqin Suspense topiladi"]
  E --> F["fallback ekranga chiqadi"]
  F --> G["Promise hal bo'ladi"]
  G --> A

E'tibor bering: React to'xtagan komponentning davomidan emas, boshidan chizadi. Shuning uchun komponent pure bo'lishi muhim. Uni necha marta chaqirsangiz ham, bir xil natija berishi kerak.

2.3 Chegara bo'lmasa

<Suspense> ni olib tashlasak-chi? To'xtagan komponent baribir eng yaqin chegarani qidiradi. Topolmasa — butun ildiz (root) chegara vazifasini bajaradi:

jsx
import { use } from "react";

const dishPromise = new Promise((resolve) => {
  setTimeout(() => resolve("Osh"), 300);
});

function Menu() {
  return <p>{use(dishPromise)}</p>;
}

export default function App() {
  return (
    <div>
      <h2>Menyu</h2>
      <Menu />
    </div>
  );
}

Ekranda (100 ms da):

text
(bo'sh)

Sarlavha "Menyu" ham yo'q, garchi uning kutishga hech qanday aloqasi bo'lmasa ham. 300 ms dan keyin ikkalasi birdan paydo bo'ladi. Haqiqiy ilovada bu — oq ekran. Xulosa: use yoki lazy ishlatsangiz, ularning tepasida doim <Suspense> bo'lsin.

Tekshirib ko'ring: Birinchi misolda Menu necha marta chaqirildi va nega Menu: ma'lumot keldi faqat bir marta chiqdi?

Javob

Uch marta: birinchi urinish, prewarm va Promise hal bo'lgandan keyingi chizish. Dastlabki ikkitasida komponent use qatorida to'xtadi — undan keyingi kod bajarilmadi. Faqat uchinchisida use qiymat qaytardi va funksiya oxirigacha ishladi. Shuning uchun render ichida yon ta'sir (so'rov yuborish, hisoblagichni oshirish) qilish xavfli — u kutilganidan ko'p marta bajariladi.

3. Nima suspend qiladi va nima qilmaydi

3.1 Uch manba

Suspense'ni faqat maxsus mexanizmlar "uyg'otadi":

Manba Nima kutiladi Qayerda o'rganamiz
use(promise) Promise'ning qiymati bugun va use()
lazy(() => import(…)) komponent kodi (alohida fayl) lazy
framework ma'lumoti Next.js server komponentlari, TanStack Query useSuspenseQuery Streaming va Suspense, TanStack Query

lazy — komponent kodini kerak bo'lgandagina yuklaydi. Kod kelguncha komponent to'xtaydi. Sayt oynasida alohida fayl import qilib bo'lmaydi. Shuning uchun import("./Reviews.jsx") o'rnida o'sha shakldagi Promise beramiz: { default: Komponent }.

jsx
import { lazy, Suspense, useState } from "react";

// import("./Reviews.jsx") o'rnida: 300 ms dan keyin modul keladi
const Reviews = lazy(() => {
  console.log("Reviews kodi yuklanmoqda");
  return new Promise((resolve) => {
    setTimeout(() => resolve({ default: ReviewsList }), 300);
  });
});

function ReviewsList() {
  return <p>Sharhlar: 2 ta</p>;
}

export default function App() {
  const [open, setOpen] = useState(false);
  return (
    <div>
      <button onClick={() => setOpen(true)}>Sharhlarni ochish</button>
      <Suspense fallback={<p>Kod yuklanmoqda…</p>}>
        {open && <Reviews />}
      </Suspense>
    </div>
  );
}

Konsolda:

text
Reviews kodi yuklanmoqda

Ekranda (bosgandan 100 ms keyin):

text
Sharhlarni ochish
Kod yuklanmoqda…

Tugma bosilmaguncha sharhlar kodi so'ralmaydi ham. Bosilgach — kod keladi, shu paytgacha fallback turadi. Mexanizm use bilan bir xil, faqat kutilgan narsa — ma'lumot emas, kod.

3.2 useEffect dagi fetch suspend qilmaydi

Sardor eski Menu ni (effekt bilan) <Suspense> ichiga qo'ydi va fallback chiqishini kutdi:

jsx
import { Suspense, useEffect, useState } from "react";

function Menu() {
  const [dishes, setDishes] = useState(null);
  useEffect(() => {
    const id = setTimeout(() => setDishes(["Osh", "Manti"]), 300);
    return () => clearTimeout(id);
  }, []);
  if (dishes === null) return <p>Menu o'zi: yuklanmoqda…</p>;
  return <p>{dishes.join(", ")}</p>;
}

function Loading() {
  useEffect(() => console.log("fallback ekranga chiqdi"), []);
  return <p>Suspense: yuklanmoqda…</p>;
}

export default function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Menu />
    </Suspense>
  );
}

Ekranda (100 ms da):

text
Menu o'zi: yuklanmoqda…

Konsol bo'sh — fallback bir marta ham chiqmadi. Menu to'xtamadi: u darhol <p> qaytardi va React buni "tayyor" deb hisobladi. Effekt chizishdan keyin ishlaydi (useEffect I), Suspense esa render paytidagi to'xtashni ushlaydi. Ular bir-birini ko'rmaydi.

Diqqat: Suspense — "sehrli" kutish emas. U faqat use, lazy va Suspense'ni qo'llaydigan kutubxonalar bilan ishlaydi. useEffect + useState bilan yozilgan yuklanish o'z if iga tayanishda davom etadi.

Tekshirib ko'ring: lazy misolida <Suspense> ni olib tashlasak, tugma bosilganda nima bo'ladi?

Javob

To'xtagan Reviews eng yaqin chegarani topolmaydi — ildizgacha chiqadi. Ildiz chegara vazifasini bajaradi, ya'ni kod kelguncha butun ilova ekrandan yo'qolishi mumkin, hatto tugma ham. «Chegara bo'lmasa» bo'limidagi oq ekranning o'zi.

4. Ichma-ich chegaralar

4.1 Bitta chegara — hammasi birga

Bitta <Suspense> ichida ikki komponent bo'lsa, React ikkalasini kutadi. Menyu 200 ms da tayyor, narxlar — 400 ms da:

jsx
import { Suspense, use } from "react";

function wait(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}
const menuPromise = wait(200, "Osh, Lag'mon, Manti");
const pricePromise = wait(400, "Osh — 35000 so'm");

function Menu() {
  return <p>{use(menuPromise)}</p>;
}

function Prices() {
  return <p>{use(pricePromise)}</p>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Yuklanmoqda…</p>}>
      <Menu />
      <Prices />
    </Suspense>
  );
}

Ekranda (300 ms da):

text
Yuklanmoqda…

Menyu 100 ms dan beri tayyor, lekin ko'rinmaydi. Parda bitta — sahnadagi eng sekin aktyor ham joyiga kelishi kerak. 400 ms da ikkalasi birga ochiladi. Jasur akaning birinchi talabi ("menyu va narx birga chiqsin") aynan shu.

4.2 Ichki chegara — bo'lak-bo'lak

Endi sharhlar sekin (800 ms). Ular menyuni ushlab turmasin — o'z pardasini beramiz:

jsx
import { Suspense, use } from "react";

function wait(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}
const menuPromise = wait(300, ["Osh", "Lag'mon", "Manti"]);
const reviewsPromise = wait(800, ["Zo'r!", "Osh juda mazali"]);

function Menu() {
  const dishes = use(menuPromise);
  console.log("menyu tayyor");
  return <p>Menyu: {dishes.join(", ")}</p>;
}

function Reviews() {
  const reviews = use(reviewsPromise);
  console.log("sharhlar tayyor");
  return <p>Sharhlar: {reviews.length} ta</p>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Sahifa yuklanmoqda…</p>}>
      <Menu />
      <Suspense fallback={<p>Sharhlar yuklanmoqda…</p>}>
        <Reviews />
      </Suspense>
    </Suspense>
  );
}

Konsolda:

text
menyu tayyor

Ekranda (500 ms da):

text
Menyu: Osh, Lag'mon, Manti
Sharhlar yuklanmoqda…

Uch bosqich bo'ldi. 0–300 ms: faqat tashqi fallback, "Sahifa yuklanmoqda…". Ichki fallback hali ko'rinmaydi — u tashqi pardaning orqasida. 300 ms: menyu chiqdi, sharhlar o'rnida ichki fallback. 800 ms: sharhlar ham chiqdi. Har use faqat o'ziga eng yaqin chegarani to'xtatadi: Reviews ning kutishi tashqi chegaraga tegmaydi.

flowchart TD
  App --> S1["Suspense: Sahifa yuklanmoqda"]
  S1 --> Menu["Menu (300 ms)"]
  S1 --> S2["Suspense: Sharhlar yuklanmoqda"]
  S2 --> Reviews["Reviews (800 ms)"]

Daraxtga qarang: Menu ning eng yaqin chegarasi — tashqi, Reviews niki — ichki. Kim to'xtasa, faqat o'z chegarasini yopadi.

4.3 Chegarani qayerga qo'yish

Bu — texnik emas, dizayn qarori. Foydalanuvchi nimani birga ko'rishi kerak?

Joylashuv Ekranda Qachon yaxshi
butun sahifaga bitta hammasi birga chiqadi qismlar bir-birisiz ma'nosiz (menyu + narx)
har komponentga alohida "popkorn": har biri o'z vaqtida deyarli hech qachon — sahifa sakraydi
ma'no bo'yicha guruh asosiy qism tez, ikkinchi darajali keyin ko'p holatda — menyu, keyin sharhlar

Qoida: chegara — foydalanuvchi uchun bir butun bo'lgan qismning chetida. Muhim kontent sekin ikkinchi darajali kontentni kutib qolmasin. Mayda bo'laklarga bo'lish ham yomon: o'nta fallback birin-ketin almashsa, ko'z charchaydi.

Tekshirib ko'ring: Ichma-ich misolda menyu 900 ms da, sharhlar 200 ms da tayyor bo'lsa, ekranda nima bo'ladi?

Javob

900 ms gacha faqat "Sahifa yuklanmoqda…". Sharhlar tayyor, lekin ular tashqi chegaraning ichida — tashqi parda yopiq turibdi. 900 ms da menyu va sharhlar birga chiqadi. Ichki chegara faqat tashqi ochilgandan keyin foyda beradi.

5. Transition bilan fallback'ni yashirish

5.1 Muammo: tab almashganda miltillash

Menyu sahifasida ikki tab bor: «Menyu» va «Sharhlar». Har tab ma'lumoti birinchi ochilganda yuklanadi. fetchTab uni Map da saqlaydi — ikkinchi marta so'ramaydi. Bu oddiy kesh, uning nozik tomonlari — keyingi darsda. Fallback ekranga necha marta chiqqanini Loading effekti sanaydi:

jsx
import { Suspense, use, useEffect, useState } from "react";

const DATA = {
  menyu: "Osh, Lag'mon, Manti",
  sharhlar: "Zo'r! Osh juda mazali",
};
const cache = new Map();
function fetchTab(tab) {
  if (!cache.has(tab)) {
    console.log("so'rov:", tab);
    const promise = new Promise((resolve) => {
      setTimeout(() => resolve(DATA[tab]), 400);
    });
    cache.set(tab, promise);
  }
  return cache.get(tab);
}

function TabContent({ tab }) {
  return <p>{use(fetchTab(tab))}</p>;
}

function Loading() {
  useEffect(() => console.log("fallback ekranga chiqdi"), []);
  return <p>Yuklanmoqda…</p>;
}

export default function App() {
  const [tab, setTab] = useState("menyu");
  return (
    <div>
      <button onClick={() => setTab("menyu")}>Menyu</button>{" "}
      <button onClick={() => setTab("sharhlar")}>Sharhlar</button>
      <Suspense fallback={<Loading />}>
        <TabContent tab={tab} />
      </Suspense>
    </div>
  );
}

Konsolda:

text
so'rov: menyu
fallback ekranga chiqdi
so'rov: sharhlar
fallback ekranga chiqdi

Ikkinchi fallback ekranga chiqdi — muammo. Mehmon menyuni o'qib turgan edi. «Sharhlar» ni bosdi — menyu yo'qolib, "Yuklanmoqda…" chiqdi, 400 ms dan keyin sharhlar. Ko'rinib turgan kontent bir lahzaga bo'sh yozuvga almashdi. Bu yomon tajriba: ekran sakraydi, foydalanuvchi "nima bo'ldi?" deb o'ylaydi.

Qiziq detal: eski menyu DOM'dan o'chmaydi. React uni display: none bilan yashiradi va ichidagi state saqlanadi. Kontent qaytsa — yashirish olinadi.

5.2 Yechim: startTransition

useTransition darsidan bilasiz: transition — "bu yangilanish shoshilinch emas". Suspense bilan birga u yana bir ma'no oladi: "tayyor bo'lmaguncha eski ekranni qoldir". Faqat App o'zgaradi:

jsx
import {
  Suspense, use, useEffect, useState, useTransition,
} from "react";

const DATA = {
  menyu: "Osh, Lag'mon, Manti",
  sharhlar: "Zo'r! Osh juda mazali",
};
const cache = new Map();
function fetchTab(tab) {
  if (!cache.has(tab)) {
    console.log("so'rov:", tab);
    const promise = new Promise((resolve) => {
      setTimeout(() => resolve(DATA[tab]), 400);
    });
    cache.set(tab, promise);
  }
  return cache.get(tab);
}

function TabContent({ tab }) {
  return <p>{use(fetchTab(tab))}</p>;
}

function Loading() {
  useEffect(() => console.log("fallback ekranga chiqdi"), []);
  return <p>Yuklanmoqda…</p>;
}

export default function App() {
  const [tab, setTab] = useState("menyu");
  const [isPending, startTransition] = useTransition();
  function selectTab(next) {
    startTransition(() => setTab(next));
  }
  return (
    <div>
      <button onClick={() => selectTab("menyu")}>Menyu</button>{" "}
      <button onClick={() => selectTab("sharhlar")}>Sharhlar</button>
      <p>{isPending ? "Kuting…" : ""}</p>
      <Suspense fallback={<Loading />}>
        <TabContent tab={tab} />
      </Suspense>
    </div>
  );
}

Konsolda:

text
so'rov: menyu
fallback ekranga chiqdi
so'rov: sharhlar

Ekranda (bosgandan 100 ms keyin):

text
Menyu Sharhlar
Kuting…
Osh, Lag'mon, Manti

Fallback faqat bir marta — sahifa ochilganda chiqdi. «Sharhlar» bosilgach, menyu joyida qoldi, tepada "Kuting…". 400 ms dan keyin sharhlar birdan chiqadi. React yangi ekranni fonda tayyorlaydi va tayyor bo'lmaguncha commit qilmaydi: eski ekran rost, yangisi hali chala.

Qadam tab isPending Ekranda
ochilish menyu false fallback, keyin menyu
«Sharhlar» bosildi fonda: sharhlar true menyu + "Kuting…"
400 ms sharhlar false sharhlar

5.3 Chegara oldindan turishi kerak

Transition hamma joyda ham fallback'ni yashirmaydi. Ikki kodni solishtiring — farq faqat <Suspense> qayerdaligida.

Chegara shart ichida — tugma bosilganda yangi paydo bo'ladi:

jsx
import { Suspense, use, useState, useTransition } from "react";

const statsPromise = new Promise((resolve) => {
  setTimeout(() => resolve("3 ta vazifa, 1 tasi bajarilgan"), 300);
});

function Stats() {
  return <p>{use(statsPromise)}</p>;
}

export default function App() {
  const [open, setOpen] = useState(false);
  const [isPending, startTransition] = useTransition();
  return (
    <div>
      <button onClick={() => startTransition(() => setOpen(true))}>
        Batafsil
      </button>
      <p>{isPending ? "Hisoblanmoqda…" : ""}</p>
      {open && (
        <Suspense fallback={<p>Tayyorlanmoqda…</p>}>
          <Stats />
        </Suspense>
      )}
    </div>
  );
}

Ekranda (bosgandan 100 ms keyin):

text
Batafsil
Tayyorlanmoqda…

Chegara tashqarida — sahifa ochilganidan beri bor, faqat ichi bo'sh edi:

jsx
import { Suspense, use, useState, useTransition } from "react";

const statsPromise = new Promise((resolve) => {
  setTimeout(() => resolve("3 ta vazifa, 1 tasi bajarilgan"), 300);
});

function Stats() {
  return <p>{use(statsPromise)}</p>;
}

export default function App() {
  const [open, setOpen] = useState(false);
  const [isPending, startTransition] = useTransition();
  return (
    <div>
      <button onClick={() => startTransition(() => setOpen(true))}>
        Batafsil
      </button>
      <p>{isPending ? "Hisoblanmoqda…" : ""}</p>
      <Suspense fallback={<p>Tayyorlanmoqda…</p>}>
        {open && <Stats />}
      </Suspense>
    </div>
  );
}

Ekranda (bosgandan 100 ms keyin):

text
Batafsil
Hisoblanmoqda…

Ikkalasida ham startTransition bor, natija esa har xil. Qoida: transition allaqachon ekranda turgan chegarani fallback'ga qaytarmaydi — u yerdagi kontentni foydalanuvchi ko'rib turibdi. Yangi paydo bo'layotgan chegara esa hech narsani yashirmaydi, shuning uchun React uning fallback'ini darhol ko'rsatadi. Panel yoki tab'ni transition bilan ochmoqchi bo'lsangiz — <Suspense> ni shartdan tashqariga qo'ying. Bu qoidaga vazifalar da yana duch kelamiz.

Tekshirib ko'ring: Tab misolida (transition bilan) foydalanuvchi «Sharhlar» ni bosib, 100 ms o'tmay «Menyu» ni bossa, nima bo'ladi?

Javob

Menyu ma'lumoti keshda tayyor — fetchTab("menyu") o'sha hal bo'lgan Promise'ni qaytaradi. Yangi transition kutilayotgan «Sharhlar» ni bosib o'tadi: ekranda menyu qoladi, "Kuting…" yo'qoladi. Sharhlar so'rovi baribir tugaydi va keshga tushadi — keyingi bosishda ular darhol chiqadi.

6. Skeleton: fallback'ning shakli

Fallback faqat matn bo'lishi shart emas. Skeleton — kelajakdagi kontent shaklidagi kulrang bo'laklar: ro'yxat bo'lsa — uchta chiziq, karta bo'lsa — to'rtburchak. Foydalanuvchi nima kelishini oldindan ko'radi. Eng muhimi, kontent kelganda sahifa sakramaydi. Bu Web Vitals dagi CLS (layout siljishi) uchun foydali.

jsx
import { Suspense, use } from "react";

const menuPromise = new Promise((resolve) => {
  setTimeout(() => resolve(["Osh", "Lag'mon", "Manti"]), 800);
});

function Menu() {
  const dishes = use(menuPromise);
  return (
    <ul>
      {dishes.map((d) => (
        <li key={d}>{d}</li>
      ))}
    </ul>
  );
}

const bar = {
  height: "1.2em",
  width: "8em",
  margin: "0.4em 0",
  background: "#d4d4d8",
  borderRadius: 4,
  listStyle: "none",
};

function MenuSkeleton() {
  return (
    <ul aria-busy="true" aria-label="Menyu yuklanmoqda">
      <li style={bar} />
      <li style={bar} />
      <li style={bar} />
    </ul>
  );
}

export default function App() {
  return (
    <section>
      <h2>«Bahor» menyusi</h2>
      <Suspense fallback={<MenuSkeleton />}>
        <Menu />
      </Suspense>
    </section>
  );
}

Ekranda (100 ms da):

text
«Bahor» menyusi

Matnda faqat sarlavha — skeleton'da yozuv yo'q. «Ishga tushir» natija oynasida esa uchta kulrang chiziq ko'rinadi, 800 ms dan keyin ular o'rnida uchta taom. Malika (ekran o'quvchi) uchun ro'yxatda aria-label="Menyu yuklanmoqda" va aria-busy="true" bor. Kulrang chiziqlarni u ko'rmaydi, lekin nima bo'layotganini eshitadi.

Skeleton bo'yicha uch maslahat:

  • O'lchami haqiqiy kontentga yaqin bo'lsin — aks holda sakrash baribir bo'ladi.
  • Juda qisqa kutishga (100 ms dan kam) skeleton kerak emas: u miltillab ketadi. React fallback'ni ko'rsatgach, uni kamida ~300 ms ushlab turadi — aynan miltillamasligi uchun.
  • Butun sahifani skeleton bilan to'ldirmang. Faqat haqiqatan kutiladigan joyga qo'ying.

7. Suspense kerak bo'lmagan joy: vazifalar ro'yxati

Suspense'ni o'rgangach, uni hamma joyga qo'ygingiz keladi. vazifalar da birinchi nomzod — server ro'yxati. Ilova ochilganda u serverdan GET qiladi (useEffect III). Uni use(promise) va <Suspense> bilan yozsa bo'lmaydimi? Kanonni qurishda buni o'lchadik.

Birinchi ochilish — 5 ta toza Chrome 155 profili (localStorage bo'sh, kalit yangi). Natija (kanon o'lchovi, aynan):

  • Ro'yxat ekranda sahifa yuklanishidayoq (load, 140–181 ms) bor — localStorage dan yoki namunadan.
  • Server 5 martadan 5 marta {"vazifalar":[],"versiya":0} qaytardi. Kalit yangi — server undan bexabar. Keyin ilova o'z ro'yxatini PUT bilan yuboradi.

GET qancha kutildi:

Birinchi ochilish: server GET javobini kutish
  • 1-ochilish39 ms
  • 2-ochilish61 ms
  • 3-ochilish160 ms
  • 4-ochilish42 ms
  • 5-ochilish112 ms

Manba: O'lchandi: vazifalar v8 (holat 04), 5 ta toza Chrome 155.0.8059.40 profili, kurs serveri, 2026-10-13

Agar ro'yxat <Suspense> ichida serverni kutganda, mehmon har ochilishda 40–160 ms fallback ko'rardi. Server ishlamasa — so'rovning chegarasi 8 soniya, shuncha vaqt "Yuklanmoqda…". Va bu kutishning natijasi — bo'sh ro'yxat. Haqiqiy ma'lumot allaqachon ekranda edi.

Shuning uchun kanonda ro'yxat Suspense'siz qoldi: avval keshdagisi, server esa fonda solishtiriladi. Effektda ma'lumot olish darsidagi "stale-while-revalidate" ning o'zi. Suspense ma'lumot hali yo'q joyda foydali. Ma'lumot bor joyda u faqat to'sqinlik qiladi.

Maslahat: "Yangi vosita qo'shaymi?" degan savolga eng yaxshi javob — o'lchov. Biz shunday qildik: variant yozildi, o'lchandi va raqam bilan rad etildi. Real loyihada ham "Suspense zamonaviy" deganga emas, foydalanuvchi nima ko'rishiga qarang.

8. Vazifalar qadami: batafsil statistika (1-qism)

8.1 Nima quramiz

vazifalar dagi oddiy statistika ("2 ta vazifa qoldi") yonida yangi «Batafsil statistika» paneli. Unda teglar jadvali (jami va bajarilgan), eng ko'p uchragan so'zlar, foiz va o'rtacha matn uzunligi bor. Ro'yxat katta bo'lsa (10 000 vazifa), bu hisob sezilarli vaqt oladi. Shuning uchun panel natijani kutib oladi — Suspense va use bilan.

Bu qadam ikki darsga bo'lingan. Bugun — hisob, panel va chegara. Keyingi darsda — Promise keshi, test va commit. Hozirgi holatingiz — useDeferredValue darsidagi qidiruv tugagan joy.

bash
git switch -c feat/batafsil-statistika

8.2 Sof hisob: batafsil-statistika.ts

Hisob — React'siz sof funksiya (Pure komponentlar g'oyasi, faqat oddiy funksiya uchun). Kirish — vazifalar, chiqish — obyekt (kanon, turi va imzosi):

ts
export type BatafsilStatistika = {
  readonly jami: number;
  readonly bajarildi: number;
  /** 0..100, butun son */
  readonly foiz: number;
  /** matn uzunligining o'rtachasi (belgi), butun */
  readonly ortachaUzunlik: number;
  /** ko'p ishlatilgani oldin, teng bo'lsa — kod tartibida */
  readonly teglar: readonly TegHisobi[];
  /** eng ko'p uchragan so'zlar (teglarsiz, 3+ harf) */
  readonly sozlar: readonly SozHisobi[];
};

export function batafsilHisobla(
  vazifalar: readonly VazifaMalumoti[],
): BatafsilStatistika {

Teglarni ajratish royxat.ts da allaqachon bor — faqat eksport qilamiz:

diff
-const TEG =
+export const TEG =
   /(?<![\p{L}\p{N}_#])#[\p{L}\p{N}_]+(?:['‘’][\p{L}\p{N}_]+)*/gu;
...
-function teglariniOl(matn: string): Set<string> {
+export function teglariniOl(matn: string): Set<string> {

Testi — tekshiruv/batafsil-statistika.test.js, to'rtta node:test testi: bo'sh ro'yxat (nolga bo'lish yo'q), foiz va teglar, so'zlar (teglarsiz, apostrof va katta harf normallangan), eng ko'pi 10 ta so'z. Hisob tez va brauzersiz tekshiriladi. Panel esa unga faqat "tayyor natijani ko'rsat" deydi.

8.3 Panel: StatistikaPaneli.tsx

Asosiy fayl. Kod kanondan aynan, izohlar telefonga sig'ishi uchun qisqa qatorlarga bo'lingan:

tsx
// StatistikaPaneli.tsx — «Batafsil statistika» (ochiladigan
// panel, 18/#01, #03–#04). Uch g'oya bir joyda:
//  - useTransition (#01): ochish — transition. Hisob tayyor
//    bo'lmaguncha React ESKI ekranni (yopiq panel) qoldiradi,
//    tugma yonida "Hisoblanmoqda…" (isPending); keyin panel
//    birdan to'liq ochiladi
//  - Suspense (#03): chegara panel ochilmasdan OLDIN bor
//    (allaqachon ko'rsatilgan) — transition paytida fallback'ga
//    almashmaydi. Fallback faqat transition'siz to'xtashda
//    (masalan, birinchi ochilish transition'siz bo'lsa) ko'rinadi
//  - use() (#04): BatafsilStatistika — keshlangan Promise'ni
//    o'qiydi
// useDeferredValue: ro'yxat o'zgarsa (katak bosildi), eski
// statistika ko'rinib turadi, yangisi fonda hisoblanadi —
// fallback miltillamaydi
import {
  Suspense,
  useDeferredValue,
  useId,
  useState,
  useTransition,
} from "react";
import { BatafsilStatistika } from "./BatafsilStatistika.tsx";
import type { VazifaMalumoti } from "../turlar.ts";

type Props = {
  vazifalar: readonly VazifaMalumoti[];
};

export function StatistikaPaneli({ vazifalar }: Props) {
  const [ochiq, setOchiq] = useState(false);
  const [kutilmoqda, boshla] = useTransition();
  const kechikkan = useDeferredValue(vazifalar);
  const panelId = useId();
  const sarlavhaId = useId();
  const eskirgan = ochiq && kechikkan !== vazifalar;

  return (
    <section id="batafsil" aria-labelledby={sarlavhaId}>
      {/* Disclosure naqshi (WAI-ARIA APG): sarlavha ichida tugma,
          holat — aria-expanded */}
      <h2 id={sarlavhaId}>
        <button
          type="button"
          id="statistika-tugma"
          aria-expanded={ochiq}
          aria-controls={panelId}
          onClick={() => boshla(() => setOchiq((o) => !o))}
        >
          Batafsil statistika
        </button>
      </h2>
      <p id="batafsil-holat" role="status">
        {kutilmoqda || eskirgan ? "Hisoblanmoqda…" : ""}
      </p>
      <Suspense
        fallback={
          <p id="batafsil-kutish">Statistika tayyorlanmoqda…</p>
        }
      >
        <div
          id={panelId}
          hidden={!ochiq}
          aria-busy={eskirgan || undefined}
        >
          {ochiq && <BatafsilStatistika vazifalar={kechikkan} />}
        </div>
      </Suspense>
    </section>
  );
}

Bugungi darsning hamma g'oyasi shu faylda:

  • Chegara shartdan tashqarida. {ochiq && …} — <Suspense> ning ichida. «Chegara oldindan turishi kerak» bo'limidagi ikkinchi variantning aynan o'zi. Shuning uchun transition paytida fallback chiqmaydi.
  • Transition — boshla(() => setOchiq(…)). Hisob tugaguncha panel yopiq qoladi, role="status" li qatorda "Hisoblanmoqda…" yoziladi. Malika buni eshitadi.
  • useDeferredValue (useDeferredValue) — panel ochiq turganda katak bosilsa, ro'yxat yangi. Yangi ro'yxat uchun hisob yangidan boshlanadi — kechikkan esa eski ro'yxatni ushlab turadi. Ekranda eski statistika qoladi, aria-busy bilan, yangisi tayyor bo'lgach almashadi. Bu ham transition — fallback yana chiqmaydi.
  • Fallback baribir bor. Transition'siz to'xtash bo'lsa (kelajakda kimdir boshla ni olib tashlasa), ilova oq bo'lib qolmaydi.

BatafsilStatistika.tsx — tayyor natijani jadvalga chizadi. Uning bosh qismi (kanon):

tsx
export function BatafsilStatistika({ vazifalar }: Props) {
  // Promise keshdan (statistika-vadasi.ts): renderda yangisi
  // yaratilmaydi
  const s = use(statistikaVadasi(vazifalar));

statistikaVadasi va undagi kesh — keyingi darsning asosiy mavzusi. Hozircha ikkala faylni (BatafsilStatistika.tsx, statistika-vadasi.ts) kanondagidek qo'shing. Ularni keyingi darsda qatorma-qator ochamiz.

App.tsx ga ikki qator:

diff
+import { StatistikaPaneli } from "./komponentlar/StatistikaPaneli.tsx";
...
+      <StatistikaPaneli vazifalar={vazifalar} />
       <p id="elon" role="status">

vazifalar.css ga — 13 qator panel uslubi (sarlavhadagi tugma, jadval chiziqlari, raqamlar o'ngda).

8.4 Haqiqiy brauzerda nima bo'ldi

Kanonning brauzer tekshiruvi (2 000 vazifa, Chrome 155) tugma bosilgandan keyin har DOM o'zgarishini yozib oldi. Format — aria-expanded|holat matni:

text
false|  →  false|Hisoblanmoqda…  →  true|

Yopiq panel → yopiq panel va "Hisoblanmoqda…" → ochiq panel, holat bo'sh. Fallback bir marta ham ko'rinmadi — tekshiruv uni alohida sanadi: 0. Katak bosilganda ham 0 — eski statistika turdi, yangisi fonda hisoblandi. Umumiy satr: 2000 ta vazifa, 667 tasi bajarilgan (33%). O'rtacha uzunlik — 17 belgi.

Hozircha commit qilmaymiz — qadam keyingi darsda tugaydi.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
butun ilova oq, keyin birdan chiqadi use/lazy tepasida <Suspense> yo'q chegarani to'xtaydigan komponentga yaqin qo'ying
fallback hech qachon chiqmaydi ma'lumot useEffect da olinadi use(promise) yoki Suspense'li kutubxona
tez qism sekinni kutib qoladi ikkalasi bitta chegarada sekin qismga ichki <Suspense>
tab almashganda kontent "Yuklanmoqda…" ga almashadi yangilanish transition emas startTransition(() => setTab(…))
transition bor, fallback baribir chiqadi <Suspense> shart ichida, yangi paydo bo'ladi chegarani shartdan tashqariga
"Yuklanmoqda…" abadiy, konsolda Promise'lar ko'payadi Promise render ichida yaratilgan Promise tashqarida yoki keshda (use())

10. Mashqlar

Mashqlar kurs/mashqlar/18/03-suspense/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): qaysi parda yopiq?

Kodni o'qing (o'zgartirmang) va savollarga javob bering. «Ishga tushir» bilan tekshirishingiz mumkin: oynani diqqat bilan kuzating.

jsx
import { Suspense, use } from "react";

function wait(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}
const menuPromise = wait(200, "Osh, Lag'mon, Manti");
const reviewsPromise = wait(600, "2 ta sharh");

function Menu() {
  return <p>{use(menuPromise)}</p>;
}

function Reviews() {
  return <p>{use(reviewsPromise)}</p>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Sahifa yuklanmoqda…</p>}>
      <Menu />
      <Suspense fallback={<p>Sharhlar yuklanmoqda…</p>}>
        <Reviews />
      </Suspense>
    </Suspense>
  );
}
  • 100 ms da ekranda nechta fallback ko'rinadi?
  • 400 ms da qaysi chegaraning fallback'i ko'rinadi — tashqi yoki ichki?
  • 800 ms da nechta fallback qoladi?
Yechim
  • 100 ms: bitta — "Sahifa yuklanmoqda…". Menu hali to'xtagan, demak tashqi chegara yopiq. Ichki fallback uning orqasida — ko'rinmaydi.
  • 400 ms: menyu 200 ms da keldi, tashqi parda ochildi. Reviews hali kutyapti — ekranda menyu va "Sharhlar yuklanmoqda…".
  • 800 ms: ikkalasi ham tayyor — fallback yo'q: "Osh, Lag'mon, Manti" va "2 ta sharh".

Uchala holat ham sinab ko'rildi. Ichki chegara faqat tashqisi ochilgandan keyin "ishga tushadi".

2-mashq (o'rta): menyu sharhlarni kutmasin

Sahifa butunlay bitta chegarada. Menyu 200 ms da tayyor, lekin sharhlar (800 ms) uni ushlab turibdi. Kodni o'zgartiring: menyu 200 ms da chiqsin, sharhlar o'rnida "Sharhlar yuklanmoqda…" tursin. Ishora: «Ichki chegara — bo'lak-bo'lak» bo'limi.

jsx
import { Suspense, use } from "react";

function wait(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}
const menuPromise = wait(200, "Menyu: Osh, Lag'mon, Manti");
const reviewsPromise = wait(800, "Sharhlar: 2 ta");

function Menu() {
  return <p>{use(menuPromise)}</p>;
}

function Reviews() {
  return <p>{use(reviewsPromise)}</p>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Yuklanmoqda…</p>}>
      <Menu />
      <Reviews />
    </Suspense>
  );
}

Ekranda (400 ms da):

text
Yuklanmoqda…
Yechim
jsx
import { Suspense, use } from "react";

function wait(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}
const menuPromise = wait(200, "Menyu: Osh, Lag'mon, Manti");
const reviewsPromise = wait(800, "Sharhlar: 2 ta");

function Menu() {
  return <p>{use(menuPromise)}</p>;
}

function Reviews() {
  return <p>{use(reviewsPromise)}</p>;
}

export default function App() {
  return (
    <Suspense fallback={<p>Yuklanmoqda…</p>}>
      <Menu />
      <Suspense fallback={<p>Sharhlar yuklanmoqda…</p>}>
        <Reviews />
      </Suspense>
    </Suspense>
  );
}

Ekranda (400 ms da):

text
Menyu: Osh, Lag'mon, Manti
Sharhlar yuklanmoqda…

Reviews endi ichki chegarani to'xtatadi, tashqisiga tegmaydi. Tashqi chegarada faqat Menu qoldi — u 200 ms da tayyor. Tashqi fallback qolsinmi? Ha: Menu ham kutadi, uning ham pardasi kerak.

3-mashq (qiyin): tab almashganda eski kontent qolsin

Tablar har almashganda menyu "Yuklanmoqda…" ga almashyapti. Uni tuzating:

  1. Tab almashishi transition bo'lsin — kutish paytida eski tab kontenti ko'rinib tursin.
  2. Kutish paytida tablar ostida <p> da "Kuting…" yozilsin, kutish yo'q paytda <p> bo'sh qolsin.
  3. Fallback faqat sahifa birinchi ochilganda chiqsin.

Ishora: useTransition — const [isPending, startTransition] = useTransition();.

jsx
import { Suspense, use, useEffect, useState } from "react";

const DATA = {
  taomlar: "Osh, Lag'mon, Manti",
  ichimliklar: "Ko'k choy — 5000 so'm",
};
const cache = new Map();
function fetchTab(tab) {
  if (!cache.has(tab)) {
    const promise = new Promise((resolve) => {
      setTimeout(() => resolve(DATA[tab]), 400);
    });
    cache.set(tab, promise);
  }
  return cache.get(tab);
}

function TabContent({ tab }) {
  return <p>{use(fetchTab(tab))}</p>;
}

function Loading() {
  useEffect(() => console.log("fallback ekranga chiqdi"), []);
  return <p>Yuklanmoqda…</p>;
}

export default function App() {
  const [tab, setTab] = useState("taomlar");
  return (
    <div>
      <button onClick={() => setTab("taomlar")}>Taomlar</button>{" "}
      <button onClick={() => setTab("ichimliklar")}>
        Ichimliklar
      </button>
      <Suspense fallback={<Loading />}>
        <TabContent tab={tab} />
      </Suspense>
    </div>
  );
}

Konsolda:

text
fallback ekranga chiqdi
fallback ekranga chiqdi
Yechim
jsx
import {
  Suspense, use, useEffect, useState, useTransition,
} from "react";

const DATA = {
  taomlar: "Osh, Lag'mon, Manti",
  ichimliklar: "Ko'k choy — 5000 so'm",
};
const cache = new Map();
function fetchTab(tab) {
  if (!cache.has(tab)) {
    const promise = new Promise((resolve) => {
      setTimeout(() => resolve(DATA[tab]), 400);
    });
    cache.set(tab, promise);
  }
  return cache.get(tab);
}

function TabContent({ tab }) {
  return <p>{use(fetchTab(tab))}</p>;
}

function Loading() {
  useEffect(() => console.log("fallback ekranga chiqdi"), []);
  return <p>Yuklanmoqda…</p>;
}

export default function App() {
  const [tab, setTab] = useState("taomlar");
  const [isPending, startTransition] = useTransition();
  function selectTab(next) {
    startTransition(() => setTab(next));
  }
  return (
    <div>
      <button onClick={() => selectTab("taomlar")}>
        Taomlar
      </button>{" "}
      <button onClick={() => selectTab("ichimliklar")}>
        Ichimliklar
      </button>
      <p>{isPending ? "Kuting…" : ""}</p>
      <Suspense fallback={<Loading />}>
        <TabContent tab={tab} />
      </Suspense>
    </div>
  );
}

Konsolda:

text
fallback ekranga chiqdi

Ekranda (bosgandan 100 ms keyin):

text
Taomlar Ichimliklar
Kuting…
Osh, Lag'mon, Manti

Fallback bir marta — ochilishda. «Ichimliklar» bosilgach, taomlar joyida qoldi, "Kuting…" chiqdi. 400 ms dan keyin choy narxi chiqadi. Chegara sahifa ochilgandan beri ekranda — shuning uchun transition uni fallback'ga qaytarmadi. <p> doim joyida turibdi, faqat matni o'zgaradi. "Kuting…" paydo bo'lganda tablar ostidagi kontent pastga sakramaydi.

4-mashq: Vazifalar qadami

feat/batafsil-statistika branch'ida «Vazifalar qadami» bo'limidagi fayllarni qo'shing: batafsil-statistika.ts va uning testi, StatistikaPaneli.tsx, BatafsilStatistika.tsx, statistika-vadasi.ts, royxat.ts dagi eksportlar, App.tsx dagi ikki qator va CSS. Commit hali qilinmaydi.

Yechim

Tekshiruv ro'yxati:

  • npm test — node:test 160 ta (oldin 156, +4 batafsil statistika). Vitest panel testi keyingi darsda qo'shiladi.
  • npm run dev — «Batafsil statistika» ni bosing: bir lahza "Hisoblanmoqda…", keyin jadval. "Statistika tayyorlanmoqda…" (fallback) ko'rinmasligi kerak.
  • Panel ochiq turganda vazifa katagini bosing — jadval yangilanadi, fallback yana chiqmaydi.
  • grep -n "Suspense" src/komponentlar/StatistikaPaneli.tsx — chegara {ochiq && …} dan tashqarida.

11. Real ishda

  • Next.js sahifasidagi loading.tsx fayli — aslida sahifa atrofidagi <Suspense> chegarasi (Maxsus fayllar). Server komponentlar sahifani bo'lak-bo'lak "oqim" bilan yuboradi — har chegara o'z vaqtida ochiladi (Streaming va Suspense).
  • TanStack Query ning useSuspenseQuery si, Relay va Apollo — Suspense bilan ishlaydigan ma'lumot kutubxonalari. Ular ichida kesh bor (TanStack Query I).
  • Router'lar sahifa almashishini transition qiladi — yangi sahifa tayyor bo'lguncha eskisi turadi. Bugungi «Transition bilan fallback'ni yashirish» bo'limi shuning o'zi.
  • Dizayn tizimlarida skeleton komponentlari tayyor turadi; chegara joylashuvi — dizayner va dasturchi birga qiladigan qaror.
  • Intervyu savollari: "Suspense qanday ishlaydi?", "Suspense useEffect dagi fetch'ni ushlaydimi?", "fallback miltillashini qanday oldini olasiz?", "chegaralarni qanday joylashtirasiz?".

Xulosa

  • <Suspense fallback> — kutish holatining bitta deklarativ joyi. Komponent faqat tayyor ma'lumotni chizadi.
  • Suspend qiladi: use(promise), lazy, Suspense'li framework va kutubxonalar. useEffect dagi fetch — yo'q.
  • To'xtagan komponent eng yaqin chegarani yopadi. Chegara yo'q bo'lsa — butun ildiz.
  • Bitta chegara — hamma birga; ichki chegara — bo'lak-bo'lak. Joylashuv — UX qarori.
  • Transition ko'rinib turgan chegarani fallback'ga qaytarmaydi. Shuning uchun <Suspense> shartdan tashqarida tursin.
  • Ma'lumot allaqachon bor joyda Suspense to'sqinlik qiladi. vazifalar ro'yxati shuning uchun Suspense'siz: o'lchovda 40–160 ms bekor kutish chiqdi.

Keyingi dars: use(): promise va context'ni o'qish — use Promise'ni qanday kutadi, nega Promise render ichida yaratilmaydi va vazifalar dagi Promise keshi qanday ishlaydi.

Manbalar

  • React: <Suspense> — react.dev/reference/react/Suspense (2026-10 holatiga, React 19.3)
  • React: use — react.dev/reference/react/use
  • React: lazy — react.dev/reference/react/lazy
  • React 19 e'loni — react.dev/blog/2024/12/05/react-19
  • React 19.3.0 manba kodi, react-dom-client.development.js: FALLBACK_THROTTLE_MS = 300 (fallback ushlab turiladigan vaqt), Prewarm
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Suspense: kutish holatini deklarativ ko'rsatish — fallback, ichma-ich chegaralar va transition — IlmHamroh