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

React lazy: komponent darajasida code splitting, oldindan yuklash va chunk xatosi

Qisqacha: const Stats = lazy(() => import("./Stats.jsx")) — komponent kodi alohida chunk (bo'lak) bo'lib qoladi va faqat birinchi chizilganda yuklanadi. Yuklanayotganda komponent to'xtaydi (suspend). Eng yaqin <Suspense> esa fallback'ini ko'rsatadi. import() qaytargan modulda komponent default eksport bo'lishi kerak. Nomli eksportni .then((m) => ({ default: m.Stats })) bilan o'raysiz. lazy ni faqat modul darajasida chaqiring — render ichida har safar yangi komponent bo'ladi. Kechikishni yashirish uchun importni oldindan boshlang: tugma ustiga kelganda yoki fokusda. Chunk yuklanmasa — xato error boundary'ga ketadi. lazy ham, brauzer ham muvaffaqiyatsiz importni eslab qoladi, shuning uchun ishonchli chiqish yo'li — sahifani yangilash.

Bu darsda

  • lazy + Suspense bilan komponentni kerak bo'lganda yuklaysiz va bundle qanday bo'linishini haqiqiy build raqamlarida ko'rasiz.
  • default eksport talabini va nomli eksportni o'rashni bilasiz.
  • Nega lazy render ichida chaqirilmasligini konsolda ko'rasiz.
  • Hover va fokusda oldindan yuklash (preload) yozasiz.
  • Chunk yuklanmasa nima bo'lishini va nega "qayta urinish" sahifani yangilashi kerakligini o'lchov bilan tushuntirasiz.

Oldin bilishingiz kerak: Suspense, Error boundary, Code splitting va chunk'lar, Dinamik import().

1. Nega bu kerak?

Code splitting darsida Vite ilovani bo'laklarga — chunk larga — bo'lishini ko'rgan edik. Dinamik import() qaerda bo'lsa, bundler shu yerda yangi chunk yaratadi (Dinamik import()). Lekin u yerda biz oddiy funksiyalarni bo'lgan edik. React komponentini qanday bo'lamiz? Komponent JSX'da <Stats /> bo'lib yoziladi, await import(...) esa render ichida ishlamaydi.

«Bahor» saytida Jasur aka uchun "Hisobotlar" sahifasi qo'shildi: grafiklar, jadvallar, eksport. Kod og'ir, lekin uni faqat Jasur aka ochadi — kuniga bir marta. Mehmonlar esa faqat menyuni ko'radi. Hozir har mehmon telefoni hisobot kodini ham yuklab oladi va tahlil qiladi. Sekin internetda bu — sahifa bir necha soniya kechroq ochiladi degani.

Hayotiy o'xshatish: restoran menyusi va desert aravachasi. Ofitsiant stolga avval asosiy menyuni olib keladi. Desert aravachasi esa oshxonada turadi va mehmon "shirinlik" desagina olib kelinadi. Har stolga aravachani oldindan sudrab kelish — ortiqcha ovora. lazy — aravachani faqat so'ralganda olib keladigan ofitsiant. Tajribali ofitsiant esa mehmon menyuning oxirgi sahifasini varaqlaganini ko'rib, aravachani oldindan yaqinlashtirib qo'yadi — bu preload.

2. lazy + Suspense

2.1 Haqiqiy loyihada qanday yoziladi

O'z Vite loyihangizda:

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

// Modul darajasida: import() hali chaqirilmaydi
const Reports = lazy(() => import("./Reports.jsx"));

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

lazy funksiya oladi, Promise emas. Shu funksiya — yuklovchi (loader). React uni <Reports /> birinchi marta chizilmoqchi bo'lganda chaqiradi. import("./Reports.jsx") shu paytda tarmoqqa so'rov yuboradi. Kod kelguncha Reports to'xtaydi — Suspense darsidagi use(promise) kabi.

sequenceDiagram
  participant B as Brauzer
  participant R as React
  participant S as Server
  B->>S: index.js (Reports'siz)
  Note over B,R: sahifa ochildi
  B->>R: «Hisobotlar» bosildi
  R->>S: import(): Reports-hash.js
  R-->>B: fallback: "yuklanmoqda…"
  S-->>R: chunk keldi
  R-->>B: Reports chizildi

Diagrammada ikki so'rovga qarang. Birinchisi — sahifa ochilganda, unda hisobot kodi yo'q. Ikkinchisi — faqat bosilgandan keyin. Bosilmasa — u hech qachon ketmaydi.

2.2 Saytda — taqlid bilan

Saytdagi «Ishga tushir» oynasi alohida fayllarni yuklay olmaydi. Shuning uchun import() ni taqlid qilamiz: 200 ms dan keyin { default: Komponent } qaytaradigan Promise. import() ham aynan shunday obyekt qaytaradi — modul obyekti, uning default maydonida — default eksport:

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

function Reports() {
  return <p>Bugun: 42 buyurtma, 1 470 000 so'm</p>;
}

// Reports.jsx faylini import() bilan yuklash taqlidi: 200 ms "tarmoq"
const LazyReports = lazy(() => {
  console.log("chunk so'raldi");
  return new Promise((resolve) => {
    setTimeout(() => resolve({ default: Reports }), 200);
  });
});

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

Konsolda:

text
chunk so'raldi

Ekranda:

text
Hisobotlar Yopish
Bugun: 42 buyurtma, 1 470 000 so'm

Ketma-ketlik: birinchi bosish — fallback (ekran tasdiqladi), 200 ms dan keyin — hisobot. Yopib, qayta ochildi — fallback yo'q, "chunk so'raldi" ham bir marta. lazy yuklovchining natijasini eslab qoladi: modul bir marta kelgach, keyingi <LazyReports /> lar darhol chiziladi.

Qadam Yuklovchi chaqirildimi Ekranda
sahifa ochildi yo'q tugmalar
1-ochilish ha (200 ms) fallback → hisobot
yopish — tugmalar
2-ochilish yo'q, natija eslangan hisobot darhol

2.3 Haqiqiy build'da nima o'zgaradi

vazifalar kanonida batafsil statistika paneli lazy ga o'tkazildi. npm run build chiqishi (haqiqiy, tekshiruv-08.txt va tekshiruv-09.txt):

text
# lazy'siz (holat 08)
dist/assets/index-BFNoHuSp.js   255.90 kB │ gzip: 81.07 kB

# lazy bilan (holat 09)
dist/assets/BatafsilStatistika-DqFnfObk.js    2.01 kB │ gzip:  0.95 kB
dist/assets/index-CGB058Sz.js               254.39 kB │ gzip: 80.56 kB

Bundler import("./BatafsilStatistika.tsx") ni ko'rib, alohida chunk yasadi. Nomi — modul nomidan, oxiridagi -DqFnfObk — tarkibdan hisoblangan hash (Code splitting). Asosiy fayl ~1.5 kB (gzip'da ~0.5 kB) kichraydi, panel kodi esa faqat ochilganda keladi.

Kichik raqam, to'g'rimi? Bu panel kichik — jadval va hisob. Grafik kutubxonasi bor hisobot sahifasida farq yuzlab kilobayt bo'ladi. Qoida o'sha: birinchi ekranda kerak bo'lmagan va og'ir narsa — lazy nomzodi.

Tekshirib ko'ring: Yuqoridagi taqlid misolida <Suspense> ni olib tashlasak, nima bo'ladi?

Javob

Komponent to'xtaganda React eng yaqin <Suspense> ni qidiradi. Topmasa — ildiz darajasida to'xtaydi: «Hisobotlar» bosilganda butun ekran yuklanish tugaguncha bo'sh yoki eski holatda qotadi (Suspense darsidagi "Chegara bo'lmasa" bo'limi). lazy doim Suspense bilan juft yuradi.

3. default eksport talabi

3.1 Nomli eksport — xato

lazy yuklovchidan kelgan obyektning default maydonini oladi. Ko'p loyihalarda esa komponentlar nomli eksport bilan yoziladi (export function Stats). Unda import() { Stats: … } qaytaradi, default yo'q:

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

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

// Stats.jsx — nomli eksport: import() natijasi { Stats }
const LazyStats = lazy(() => Promise.resolve({ Stats }));

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

Konsolda:

text
lazy: Expected the result of a dynamic import() call. Instead received: [object Object]
Your code should look like: 
  const MyComponent = lazy(() => import('./MyComponent'))
lazy: Expected the result of a dynamic import() call. Instead received: [object Object]
Your code should look like: 
  const MyComponent = lazy(() => import('./MyComponent'))
Uncaught Error: Element type is invalid. Received a promise that resolves to: undefined. Lazy element type must resolve to a class or function.

Tarjima: "lazy: dinamik import() chaqiruvining natijasi kutilgan edi. Uning o'rniga [object Object] keldi. Kodingiz shunday bo'lishi kerak: …". Oxirgi qator: "Element turi noto'g'ri. undefined ga hal bo'ladigan Promise keldi. Lazy element turi class yoki funksiyaga hal bo'lishi kerak". undefined — default maydoni. Ogohlantirish ikki marta — React komponentni qayta urindi.

3.2 Davosi — o'rash

Komponent faylini o'zgartirmasdan, natijani { default } ga o'raymiz:

jsx
const Stats = lazy(() =>
  import("./Stats.jsx").then((m) => ({ default: m.Stats })),
);

.then — Promise zanjiri: modul kelgach, undan kerakli eksportni olib, default nomi bilan qaytaramiz. vazifalar kanoni aynan shunday qiladi — loyihada hamma komponent nomli eksport bilan.

4. lazy — faqat modul darajasida

4.1 Render ichida — har safar yangi

Tabiiy xato: lazy ni komponent ichida yozish. Har render — yangi lazy chaqiruvi — React uchun yangi komponent turi:

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

function Reports() {
  return <p>Hisobot tayyor</p>;
}

export default function App() {
  const [note, setNote] = useState("");
  // ❌ har renderda yangi lazy — yangi komponent
  const LazyReports = lazy(() => {
    console.log("chunk so'raldi");
    return new Promise((resolve) => {
      setTimeout(() => resolve({ default: Reports }), 50);
    });
  });
  return (
    <div>
      <label>
        Izoh{" "}
        <input
          value={note}
          onChange={(e) => setNote(e.target.value)}
        />
      </label>
      <Suspense fallback={<p>Yuklanmoqda…</p>}>
        <LazyReports />
      </Suspense>
    </div>
  );
}

Konsolda:

text
chunk so'raldi
chunk so'raldi

Izohga matn yozildi — App qayta chizildi — yangi lazy — yangi tur. React eski LazyReports ni olib tashlab, yangisini qo'ydi va yana yukladi. Har harfda fallback miltillaydi. LazyReports ichida state bo'lsa — u ham har safar yo'qoladi (State'ning saqlanishi: tur o'zgarsa, state yo'qoladi).

Haqiqiy loyihada import() ni brauzer keshlaydi, shuning uchun tarmoq so'rovi bo'lmasligi mumkin. Lekin miltillash va state yo'qolishi — baribir. vazifalar kanonini qurishda ham shu xato urinib ko'rildi: React Compiler lint'i uni static-components qoidasi bilan to'xtatdi. Davosi — lazy ni komponentdan tashqariga, fayl boshiga chiqarish.

5. Oldindan yuklash (preload)

5.1 Kechikishni yashirish

lazy yuklashni birinchi chizish paytida boshlaydi — ya'ni foydalanuvchi bosgandan keyin. Sekin internetda u har safar fallback'ni ko'radi. Lekin bosishdan oldin ham signal bor: sichqoncha tugma ustiga keldi (pointerenter) yoki Bobur Tab bilan tugmaga keldi (focus). Bu orada 100–300 ms o'tadi — kichik chunk shu vaqtda yetib keladi.

Hiyla — yuklovchini o'zimiz chaqirib, natijasini eslab qolish. lazy ham, oldindan yuklash ham bitta Promise'ni kutadi:

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

function Reports() {
  return <p>Bugun: 42 buyurtma</p>;
}

let modulePromise = null;
function loadReports() {
  // ??= — faqat birinchi chaqiruvda yuklaydi
  modulePromise ??= new Promise((resolve) => {
    console.log("chunk so'raldi");
    setTimeout(() => resolve({ default: Reports }), 200);
  });
  return modulePromise;
}

const LazyReports = lazy(loadReports);

export default function App() {
  const [open, setOpen] = useState(false);
  return (
    <div>
      {/* Haqiqiy loyihada: onPointerEnter va onFocus */}
      <button onClick={loadReports}>Ko'rib chiqish</button>{" "}
      <button onClick={() => setOpen(true)}>Hisobotlar</button>
      <Suspense fallback={<p>Hisobotlar yuklanmoqda…</p>}>
        {open && <LazyReports />}
      </Suspense>
    </div>
  );
}

Konsolda:

text
chunk so'raldi

Ekranda (bosgandan 210 ms keyin):

text
Ko'rib chiqish Hisobotlar
Hisobotlar yuklanmoqda…

«Ko'rib chiqish» — sichqoncha tugma ustiga kelgan payt o'rnida (sinov vositasida "ustiga kelish" yo'q, shuning uchun bosish bilan). Chunk 200 ms da keldi, so'rov ikkinchi marta ketmadi. Lekin «Hisobotlar» dan 210 ms keyin ham ekranda — fallback! Kod tayyor-ku?

Ikki sabab. Birinchisi: lazy Promise tayyor ekanini faqat .then orqali, bir lahza kechikib biladi (Microtask). Bosish esa shoshilinch yangilanish — React shu lahzada fallback'ni chizib ulguradi. Ikkinchisi: fallback bir marta chiqsa, React uni kamida ~300 ms ushlab turadi — ekran miltillamasin deb. Natijada oldindan yuklash bekor ketdi.

Davosi — Suspense darsidan tanish: ochishni transition bilan qilish. Transition'da React yangi ekran tayyor bo'lguncha eskisini qoldiradi, fallback'ni umuman chizmaydi:

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

function Reports() {
  return <p>Bugun: 42 buyurtma</p>;
}

let modulePromise = null;
function loadReports() {
  modulePromise ??= new Promise((resolve) => {
    console.log("chunk so'raldi");
    setTimeout(() => resolve({ default: Reports }), 200);
  });
  return modulePromise;
}

const LazyReports = lazy(loadReports);

export default function App() {
  const [open, setOpen] = useState(false);
  function handleOpen() {
    startTransition(() => setOpen(true)); // ✅ fallback'siz
  }
  return (
    <div>
      <button onClick={loadReports}>Ko'rib chiqish</button>{" "}
      <button onClick={handleOpen}>Hisobotlar</button>
      <Suspense fallback={<p>Hisobotlar yuklanmoqda…</p>}>
        {open && <LazyReports />}
      </Suspense>
    </div>
  );
}

Konsolda:

text
chunk so'raldi

Ekranda (bosgandan 160 ms keyin):

text
Ko'rib chiqish Hisobotlar
Bugun: 42 buyurtma

Endi hisobot darhol: fallback bir marta ham chizilmadi. Preload va transition — juftlik. Preload kodni oldindan olib keladi, transition esa o'sha lahzalik kechikishni yashiradi. Chunk hali kelmagan bo'lsa ham transition foydali: foydalanuvchi eski ekranni ko'rib turadi, keyin birdan yangisini.

??= — mantiqiy tayinlash: "o'zgaruvchi null bo'lsa — yukla va saqla". Shu bitta qator "bitta modul — bitta Promise" ni ta'minlaydi. use() darsidagi kesh g'oyasi — "talonni cho'ntakda saqlash" — bu yerda ham ishlaydi.

5.2 Qaysi signal

Signal Qachon Kimga
onPointerEnter sichqoncha tugma ustiga keldi kompyuter
onFocus Tab bilan tugmaga keldi Bobur (klaviatura), ekran o'quvchi
onTouchStart barmoq tekkan payt telefon (bosishdan ~100 ms oldin)
bo'sh vaqtda (requestIdleCallback) sahifa yuklanib, brauzer bo'sh hamma (ehtimoliy sahifalar)

Faqat onPointerEnter ni yozib, onFocus ni unutish — klaviatura foydalanuvchilarini chetda qoldirish. vazifalar ikkalasini ham ishlatadi.

6. Chunk yuklanmasa

6.1 Xato — chegaraga

Mehmon metroda: internet uzildi, chunk kelmadi. import() rad etildi. lazy uni render xatosi kabi tashlaydi — Error boundary ushlaydi:

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

class ErrorBoundary extends Component {
  state = { error: null };
  static getDerivedStateFromError(error) {
    return { error };
  }
  reset = () => this.setState({ error: null });
  render() {
    if (this.state.error) {
      return (
        <div role="alert">
          <p>{this.state.error.message}</p>
          <button onClick={this.reset}>Qayta urinish</button>
        </div>
      );
    }
    return this.props.children;
  }
}

let calls = 0;
const LazyReports = lazy(() => {
  calls++;
  console.log("yuklovchi chaqirildi:", calls);
  if (calls === 1) {
    return Promise.reject(new Error("chunk yuklanmadi"));
  }
  return Promise.resolve({ default: () => <p>Hisobot</p> });
});

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

Konsolda:

text
yuklovchi chaqirildi: 1
Error: chunk yuklanmadi
The above error occurred in one of your React components.
React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.
Error: chunk yuklanmadi
The above error occurred in one of your React components.
React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.

Ekranda:

text
chunk yuklanmadi
Qayta urinish

Yuklovchi ikkinchi marta "muvaffaqiyatli" bo'lishga tayyor edi. Lekin «Qayta urinish» dan keyin ham — o'sha xato. Konsolda "yuklovchi chaqirildi" bir marta. Sabab: lazy yuklovchi natijasini eslab qoladi — muvaffaqiyatini ham, xatosini ham. Chegara tiklandi, React LazyReports ni qayta chizdi, lazy esa eslab qolgan rad etilishni yana tashladi.

6.2 Brauzer ham eslab qoladi

"Unda yangi lazy yasab, qayta urinamiz" degan fikr tug'iladi. Kanonni qurishda aynan shu sinab ko'rildi: yuklovchi ichida 3 urinish, 0.5 va 1 soniya kutish bilan. Haqiqiy natija (natijalar/09-chunk-xato.txt, Chrome 155):

text
❌ chunk xatosi: 3 urinish, keyin panel ichida xabar (role=alert), ro'yxat joyida: [1,"alert",false] (kutilgan [3,"alert",true])
# 3 urinish — tarmoqqa BITTA so'rov: brauzer muvaffaqiyatsiz dynamic import'ni modul xaritasida eslab qoladi.

Uch urinish — tarmoqqa bitta so'rov. Brauzer bir manzildagi modulni bir marta yuklaydi va natijasini — xatoni ham — modul xaritasida saqlaydi (Modul semantikasi). Bir xil import("./x.js") ikkinchi marta tarmoqqa chiqmaydi, o'sha xatoni qaytaradi. Sahifa ichida qayta urinish foydasiz.

Ishonchli chiqish yo'li — sahifani yangilash: yangi sahifa — yangi modul xaritasi. Kanondagi qaror shu: chunk kelmagan bo'lsa, «Qayta urinish» location.reload() qiladi. Yozilayotgan qoralama yo'qolmaydi — u pagehide da saqlanadi (16-qismdan).

6.3 Deploy'dan keyingi xato

Chunk yuklanmasligining yana bir mashhur sababi — internet emas, yangi versiya. Foydalanuvchi sahifani soat 10:00 da ochdi. 10:30 da yangi versiya chiqdi: Reports-a1b2.js o'rniga endi Reports-c3d4.js. Eski sahifa 10:45 da eski nomni so'raydi — server 404 qaytaradi. Bu holatda ham davo — sahifani yangilash: yangi index.html yangi nomlarni biladi. Ko'p jamoalar vite:preloadError hodisasini tinglab, avtomatik yangilaydi (Vite: production build).

Tekshirib ko'ring: Nega chunk xatosida XatoChegarasi ning oddiy «Qayta urinish» i (state'ni tozalash) yordam bermaydi, hisob xatosida esa yordam beradi?

Javob

Hisob xatosi — yangi Promise bilan tuzalishi mumkin: kesh rad etilgan Promise'ni o'chiradi (Error boundary dagi vada.catch), keyingi render yangisini so'raydi. Chunk xatosida esa ikki joy xatoni eslab qolgan: lazy ning o'zi va brauzerning modul xaritasi. Chegarani tozalash ularning hech birini unuttirmaydi — faqat sahifani yangilash.

7. Nimani bo'lish kerak, nimani yo'q

Har lazy — yangi tarmoq so'rovi va fallback. Ortiqcha bo'lish ham zarar:

Nomzod lazy mi? Nega
alohida sahifa (marshrut) ha foydalanuvchi bir vaqtda bitta sahifani ko'radi (React Router)
kamdan-kam ochiladigan og'ir panel (hisobot, grafik, muharrir) ha ko'pchilik uni hech ochmaydi
modal oyna ichi ko'pincha ha bosilgandan keyin kerak; preload bilan
birinchi ekrandagi kichik tugma, forma yo'q baribir darhol kerak — faqat qo'shimcha so'rov
tez-tez ishlatiladigan kichik komponent yo'q so'rov xarajati foydadan katta

vazifalar kanonida bu jadval o'lchov bilan tekshirildi. Eksport va Import komponentlari ham lazy qilib ko'rildi (natijalar/09-eksport-lazy.txt):

text
assets/Eksport-BqVo46EW.js             732     446     401
assets/Import-DsfyMWBz.js             2083    1020     907
assets/index-iA60FFjU.js            252260   79015   68707

Asosiy fayl gzip'da 79 571 dan 79 015 baytga tushdi — ~0.5 KB. Evaziga ikki qo'shimcha so'rov. Va brauzer tekshiruvi 40-qadamda yiqildi:

text
Error: No element found for selector: #eksport-tugma

Tugma endi kechroq paydo bo'ladi — ssenariy uni topa olmadi. Foydalanuvchi ham aynan shuni sezardi: sahifa ochildi, eksport tugmasi esa bir lahzadan keyin "otilib chiqadi". Qaror: kirmadi. Yarim kilobayt uchun sakraydigan interfeys — yomon savdo.

8. Vazifalar qadami: statistika paneli — lazy (holat 09)

8.1 StatistikaPaneli.tsx

Kanondan aynan (izohlar telefon uchun qatorlarga qayta bo'lingan):

tsx
let yuklandi = false; // modul bir marta kelsa — keyin doim bor

// Modul Promise'i bitta: oldindan yuklash (hover, fokus) va lazy() —
// ikkalasi shuni kutadi. Sahifa ichida qayta urinish FOYDASIZ:
// brauzer muvaffaqiyatsiz import'ni ham eslab qoladi (o'lchandi,
// Chrome 155: uch urinish — tarmoqqa bitta so'rov). Chunk kelmasa —
// sahifani yangilash
let modul: Promise<typeof import("./BatafsilStatistika.tsx")> | null =
  null;
function modulniOl() {
  modul ??= import("./BatafsilStatistika.tsx").then((m) => {
    yuklandi = true;
    return m;
  });
  return modul;
}

// lazy() — modul darajasida (renderda yaratilsa, har renderda yangi
// komponent: holati yo'qoladi; React Compiler lint'i ham to'xtatadi —
// "static-components", o'lchandi). Faqat default export bilan
// ishlaydi: nomli eksportni { default } ga o'raymiz. Rad etilgan
// yuklashni lazy ESLAB QOLADI — chunk baribir kelmasa, chiqish yo'li
// sahifani yangilash
const BatafsilStatistika = lazy(() =>
  modulniOl().then((m) => ({ default: m.BatafsilStatistika })),
);

// Oldindan yuklash: xato bu yerda jim — ochilganda chegara ko'rsatadi
function oldindanYukla() {
  modulniOl().catch(() => {});
}

Darsning hamma qoidasi shu yerda: bitta modul Promise'i (??=), lazy — modul darajasida, nomli eksport — { default } ga o'ralgan. Uch yangi detal:

  • typeof import("…") — TypeScript'da modulning turi. import() turni ham beradi, kodni esa yuklamaydi.
  • yuklandi — kod kelgan-kelmaganini biladi. Chegaraning «Qayta urinish» i shunga qarab ishlaydi (pastda).
  • oldindanYukla dagi .catch(() => {}) — oldindan yuklash muvaffaqiyatsiz bo'lsa, bu yerda jim. Foydalanuvchi hali hech narsa bosmagan. Panel ochilganda o'sha rad etilgan Promise chegaraga yetadi.

Tugmaga ikki signal qo'shildi:

diff
           onClick={() => boshla(() => setOchiq((o) => !o))}
+          onPointerEnter={oldindanYukla}
+          onFocus={oldindanYukla}

boshla — useTransition: panel transition bilan ochiladi, shuning uchun chunk kutilayotganda eski ekran turadi, fallback esa chiqmaydi (Suspense dagi "Transition bilan fallback'ni yashirish").

8.2 Chegaradagi «Qayta urinish»

tsx
            <button
              type="button"
              onClick={() => {
                // Kod (chunk) kelmagan — lazy xatoni eslab
                // qolgan: sahifani yangilash (qoralama
                // pagehide'da saqlanadi). Kod bor — xato
                // hisobda: chegarani tiklash kifoya
                if (yuklandi) {
                  qaytaUrin();
                } else {
                  location.reload();
                }
              }}
            >
              Qayta urinish
            </button>

Bitta tugma — ikki xil davo. Hisob xatosi — chegarani tozalash yetarli. Kod kelmagan — sahifani yangilash.

8.3 Haqiqiy brauzerda

e2e-18 (Chrome 155, natija-18-09.txt):

text
✅ lazy: ochilishda panel kodi yuklanmagan: 0
✅ lazy: tugma ustiga kelinganda — oldindan yuklandi (bosilmasdan): [1,"false"]
✅ chunk xatosi: panel ichida xabar (role=alert), qolgan ilova joyida: [1,"alert",true,true]
✅ chunk xatosi: «Qayta urinish» — sahifa yangilandi, panel ochildi: "true"

Ochilishda chunk so'ralmagan (0), tugma ustiga kelinganda — 1 ta so'rov, panel esa hali yopiq ("false"). Chunk so'rovi ataylab to'silganda — xabar faqat panel ichida, ilova joyida. Ruxsat berilgach, «Qayta urinish» sahifani yangiladi va panel ochildi.

8.4 Commit

bash
git switch -c perf/statistika-lazy
git add -A
git commit

Commit (kanon): perf: statistika paneli lazy — hover/fokusda oldindan yuklash. Tekshiruvlar: node:test ℹ tests 163, Vitest Tests 36 passed (36), lint, format, tip — toza. Brauzer — 159/159, e2e-18 — 26/26. Hajm (gzip -9, tekshiruv-09.txt): asosiy fayl 80 077 → 79 571 B, panel chunk'i — 958 B. Diff: 2 fayl, +57 −7.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
lazy: Expected the result of a dynamic import() call nomli eksport, default yo'q .then((m) => ({ default: m.X }))
har harfda fallback miltillaydi lazy render ichida fayl boshiga, modul darajasiga
oq ekran yoki ildiz qotdi Suspense yo'q lazy ustida <Suspense>
chunk xatosi — oq ekran error boundary yo'q chegara + "sahifani yangilash"
«Qayta urinish» — yana o'sha xato lazy va brauzer xatoni eslab qolgan location.reload()
deploy'dan keyin 404 chunk eski sahifa eski nomni so'raydi vite:preloadError → yangilash
bosganda har safar kechikish preload yo'q onPointerEnter + onFocus

10. Mashqlar

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

1-mashq (oson): to'g'rimi?

ha yoki yo'q:

  • const X = lazy(() => import("./X.jsx")) ni komponent ichida yozsa bo'ladimi?
  • lazy komponent <Suspense> siz ishlaydimi (yuklanayotganda)?
  • import() nomli eksport qaytarsa, lazy uni o'zi topadimi?
  • Chunk yuklanmasa, error boundary ushlaydimi?
Yechim
  • yo'q — har renderda yangi komponent turi: state yo'qoladi, fallback miltillaydi.
  • yo'q — to'xtagan komponent uchun eng yaqin Suspense kerak.
  • yo'q — faqat default; nomlisini { default: m.X } ga o'rang.
  • ha — rad etilgan yuklash render xatosi kabi tashlanadi.

2-mashq (o'rta): nomli eksportni o'rang

Yuklovchi (taqlid) { Gallery } — nomli eksport qaytaradi va dastur yiqiladi. Yuklovchini o'zgartirmasdan, uning natijasini { default } ga o'rang. Ishora: «Davosi — o'rash» bo'limi; .then.

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

function Gallery() {
  return <p>Taomlar galereyasi: 12 rasm</p>;
}

// Gallery.jsx ni import() bilan yuklash taqlidi — o'zgartirmang
function loadGallery() {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ Gallery }), 100);
  });
}

const LazyGallery = lazy(loadGallery);

export default function App() {
  return (
    <Suspense fallback={<p>Galereya yuklanmoqda…</p>}>
      <LazyGallery />
    </Suspense>
  );
}

Konsolda:

text
lazy: Expected the result of a dynamic import() call. Instead received: [object Object]
Your code should look like: 
  const MyComponent = lazy(() => import('./MyComponent'))
lazy: Expected the result of a dynamic import() call. Instead received: [object Object]
Your code should look like: 
  const MyComponent = lazy(() => import('./MyComponent'))
Uncaught Error: Element type is invalid. Received a promise that resolves to: undefined. Lazy element type must resolve to a class or function.
Yechim
jsx
import { lazy, Suspense } from "react";

function Gallery() {
  return <p>Taomlar galereyasi: 12 rasm</p>;
}

function loadGallery() {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ Gallery }), 100);
  });
}

const LazyGallery = lazy(() =>
  loadGallery().then((m) => ({ default: m.Gallery })),
);

export default function App() {
  return (
    <Suspense fallback={<p>Galereya yuklanmoqda…</p>}>
      <LazyGallery />
    </Suspense>
  );
}

Ekranda:

text
Taomlar galereyasi: 12 rasm

.then modul obyektidan Gallery ni olib, default nomi bilan qaytardi. Haqiqiy loyihada loadGallery() o'rnida import("./Gallery.jsx") turadi — kod bir xil.

3-mashq (qiyin): fokusda oldindan yuklash

Retseptlar paneli — og'ir (taqlid: 300 ms). Talablar:

  1. lazy modul darajasida, yuklovchi — bitta Promise'ni eslab qoladi (??=).
  2. «Retseptlar» tugmasiga fokus kelganda (Bobur Tab bilan keldi) yuklash boshlansin. Sinovda fokus o'rniga «Tayyorlash» tugmasi bosiladi — u ham shu funksiyani chaqirsin.
  3. «Retseptlar» bosilganda panel transition bilan ochilsin: yuklash tugagan bo'lsa — fallback'siz. Konsolda "chunk so'raldi" bir marta. Ishora: «Kechikishni yashirish» bo'limi.
Yechim
jsx
import { lazy, startTransition, Suspense, useState } from "react";

function Recipes() {
  return <p>Osh retsepti: guruch, go'sht, sabzi</p>;
}

let recipesPromise = null;
function loadRecipes() {
  recipesPromise ??= new Promise((resolve) => {
    console.log("chunk so'raldi");
    setTimeout(() => resolve({ default: Recipes }), 300);
  });
  return recipesPromise;
}

const LazyRecipes = lazy(loadRecipes);

export default function App() {
  const [open, setOpen] = useState(false);
  return (
    <div>
      <button onClick={loadRecipes}>Tayyorlash</button>{" "}
      <button
        onFocus={loadRecipes}
        onClick={() => startTransition(() => setOpen(true))}
      >
        Retseptlar
      </button>
      <Suspense fallback={<p>Retseptlar yuklanmoqda…</p>}>
        {open && <LazyRecipes />}
      </Suspense>
    </div>
  );
}

Konsolda:

text
chunk so'raldi

Ekranda:

text
Tayyorlash Retseptlar
Osh retsepti: guruch, go'sht, sabzi

«Tayyorlash» yuklashni boshladi, 300 ms da modul tayyor. «Retseptlar» bosilganda onFocus yana loadRecipes ni chaqirdi — bosish ham fokus beradi. Lekin ??= tufayli yangi so'rov yo'q. 100 ms da ekranda retsept: transition fallback'ni chizmadi, lazy esa tayyor Promise'ni oldi. startTransition siz bu yerda ~300 ms fallback ko'rinardi. Haqiqiy loyihada «Tayyorlash» tugmasi kerak emas — onFocus va onPointerEnter kifoya.

4-mashq: Vazifalar qadami

perf/statistika-lazy branch'ida StatistikaPaneli.tsx ni «Vazifalar qadami» bo'limi bo'yicha o'zgartiring: modulniOl, modul darajasidagi lazy, oldindanYukla (hover va fokus), chegaradagi ikki yo'lli «Qayta urinish». Build qilib, chunk paydo bo'lganini tekshiring. Commit qiling va PR oching.

Yechim

Tekshiruv ro'yxati:

  • npm run build — dist/assets/BatafsilStatistika-<hash>.js paydo bo'ldi (~2 kB, gzip ~1 kB), asosiy fayl ~0.5 KB (gzip) kichraydi.
  • npm run preview → DevTools → Network: sahifa ochilganda BatafsilStatistika-…js yo'q; tugma ustiga kelganda — bor (hali bosilmagan).
  • Network'da shu faylni to'sing (Request blocking) va panelni oching — xabar faqat panel ichida. To'siqni olib, «Qayta urinish» — sahifa yangilanadi.
  • npm test — node 163, Vitest 36. Commit — perf: statistika paneli lazy — hover/fokusda oldindan yuklash.

11. Real ishda

  • Marshrut darajasida bo'lish — eng ko'p uchraydigan holat: har sahifa — o'z chunk'i. React Router'da lazy marshrut xususiyati bor (React Router); Next.js buni avtomatik qiladi (Next.js performance).
  • Bundle tahlili — rollup-plugin-visualizer kabi vositalar bilan "eng og'ir nima?" savoliga javob topib, keyin lazy qilinadi (Vite: production build). Taxmin bilan emas — o'lchab.
  • Deploy'dan keyingi chunk xatolari — production'dagi eng ko'p uchraydigan xatolardan biri. Sentry'da ChunkLoadError / Failed to fetch dynamically imported module — tanish manzara.
  • Intervyu savollari: "Code splitting nima va React'da qanday qilinadi?", "nega lazy default eksport talab qiladi?", "chunk yuklanmasa nima qilasiz?", "preload qachon kerak?".

Xulosa

  • lazy(() => import("./X")) — komponent alohida chunk; birinchi chizishda yuklanadi, <Suspense> fallback bilan.
  • default eksport shart; nomlisi — .then((m) => ({ default: m.X })).
  • lazy — faqat modul darajasida; render ichida — har safar yangi tur, state yo'qoladi.
  • Preload: bitta modul Promise'i (??=) + onPointerEnter/onFocus.
  • Chunk xatosini lazy ham, brauzer ham eslab qoladi (3 urinish — 1 so'rov) → chegara + sahifani yangilash.
  • vazifalar: statistika paneli — 958 B chunk, hover/fokusda oldindan; Eksport/Import lazy — o'lchandi va kirmadi.

Keyingi dars: Portallar — modal va toast'ni DOM'ning boshqa joyiga chiqarish, lekin React daraxtida qoldirish.

Manbalar

  • React: lazy — react.dev/reference/react/lazy (2026-10 holati, React 19.3)
  • React: <Suspense> — react.dev/reference/react/Suspense
  • MDN: import() — developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/import
  • Vite: "Load Error Handling" (vite:preloadError) — vite.dev/guide/build
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
React lazy: komponent darajasida code splitting, oldindan yuklash va chunk xatosi — IlmHamroh