Mundarija (33)
- Bu darsda
- 1. Nega bu kerak?
- 2. lazy + Suspense
- 2.1 Haqiqiy loyihada qanday yoziladi
- 2.2 Saytda — taqlid bilan
- 2.3 Haqiqiy build'da nima o'zgaradi
- 3. default eksport talabi
- 3.1 Nomli eksport — xato
- 3.2 Davosi — o'rash
- 4. lazy — faqat modul darajasida
- 4.1 Render ichida — har safar yangi
- 5. Oldindan yuklash (preload)
- 5.1 Kechikishni yashirish
- 5.2 Qaysi signal
- 6. Chunk yuklanmasa
- 6.1 Xato — chegaraga
- 6.2 Brauzer ham eslab qoladi
- 6.3 Deploy'dan keyingi xato
- 7. Nimani bo'lish kerak, nimani yo'q
- 8. Vazifalar qadami: statistika paneli — lazy (holat 09)
- 8.1 StatistikaPaneli.tsx
- 8.2 Chegaradagi «Qayta urinish»
- 8.3 Haqiqiy brauzerda
- 8.4 Commit
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): to'g'rimi?
- 2-mashq (o'rta): nomli eksportni o'rang
- 3-mashq (qiyin): fokusda oldindan yuklash
- 4-mashq: Vazifalar qadami
- 11. Real ishda
- Xulosa
- Manbalar
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 komponentdefaulteksport bo'lishi kerak. Nomli eksportni.then((m) => ({ default: m.Stats }))bilan o'raysiz.lazyni 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.lazyham, brauzer ham muvaffaqiyatsiz importni eslab qoladi, shuning uchun ishonchli chiqish yo'li — sahifani yangilash.
Bu darsda
lazy+Suspensebilan komponentni kerak bo'lganda yuklaysiz va bundle qanday bo'linishini haqiqiy build raqamlarida ko'rasiz.defaulteksport talabini va nomli eksportni o'rashni bilasiz.- Nega
lazyrender 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:
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 chizildiDiagrammada 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:
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:
chunk so'raldiEkranda:
Hisobotlar Yopish
Bugun: 42 buyurtma, 1 470 000 so'mKetma-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):
# 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 kBBundler 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:
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:
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:
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:
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:
chunk so'raldi
chunk so'raldiIzohga 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:
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:
chunk so'raldiEkranda (bosgandan 210 ms keyin):
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:
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:
chunk so'raldiEkranda (bosgandan 160 ms keyin):
Ko'rib chiqish Hisobotlar
Bugun: 42 buyurtmaEndi 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:
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:
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:
chunk yuklanmadi
Qayta urinishYuklovchi 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):
❌ 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
XatoChegarasining 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):
assets/Eksport-BqVo46EW.js 732 446 401
assets/Import-DsfyMWBz.js 2083 1020 907
assets/index-iA60FFjU.js 252260 79015 68707Asosiy 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:
Error: No element found for selector: #eksport-tugmaTugma 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):
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).oldindanYukladagi.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:
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»
<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):
✅ 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
git switch -c perf/statistika-lazy
git add -A
git commitCommit (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?lazykomponent<Suspense>siz ishlaydimi (yuklanayotganda)?import()nomli eksport qaytarsa,lazyuni 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
Suspensekerak. - 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.
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:
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
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:
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:
lazymodul darajasida, yuklovchi — bitta Promise'ni eslab qoladi (??=).- «Retseptlar» tugmasiga fokus kelganda (Bobur Tab bilan keldi) yuklash boshlansin. Sinovda fokus o'rniga «Tayyorlash» tugmasi bosiladi — u ham shu funksiyani chaqirsin.
- «Retseptlar» bosilganda panel transition bilan ochilsin: yuklash tugagan bo'lsa — fallback'siz. Konsolda "chunk so'raldi" bir marta. Ishora: «Kechikishni yashirish» bo'limi.
Yechim
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:
chunk so'raldiEkranda:
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>.jspaydo bo'ldi (~2 kB, gzip ~1 kB), asosiy fayl ~0.5 KB (gzip) kichraydi.npm run preview→ DevTools → Network: sahifa ochilgandaBatafsilStatistika-…jsyo'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
lazymarshrut xususiyati bor (React Router); Next.js buni avtomatik qiladi (Next.js performance). - Bundle tahlili —
rollup-plugin-visualizerkabi vositalar bilan "eng og'ir nima?" savoliga javob topib, keyinlazyqilinadi (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
lazydefault eksport talab qiladi?", "chunk yuklanmasa nima qilasiz?", "preload qachon kerak?".
Xulosa
lazy(() => import("./X"))— komponent alohida chunk; birinchi chizishda yuklanadi,<Suspense>fallback bilan.defaulteksport 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
lazyham, brauzer ham eslab qoladi (3 urinish — 1 so'rov) → chegara + sahifani yangilash. vazifalar: statistika paneli — 958 B chunk, hover/fokusda oldindan; Eksport/Importlazy— 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
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!