Code Splitting — dynamic import, lazy, Suspense

~15 daq · React · Performance

Muammodan boshlaylik

Ilovangiz o'sdi: 40 ta sahifa, admin panel, grafiklar kutubxonasi, Markdown muharriri, PDF eksport. npm run build:

dist/assets/index-a1b2c3.js    1,840 kB │ gzip: 560 kB
(!) Some chunks are larger than 500 kB after minification.

Foydalanuvchi bosh sahifani ochadi — brauzer butun ilovani yuklaydi: admin panelni (u admin emas), grafiklarni (bosh sahifada grafik yo'q), PDF eksportni (hech qachon bosmasligi mumkin). Sekin 3G da — bir necha soniya bo'sh ekran.

Yechim — code splitting (kodni bo'laklash): kodni bir nechta faylga bo'lib, har birini faqat kerak bo'lganda yuklash.


Dynamic import

Oddiy (statik) import — fayl yuklanganda darhol:

import { formatlash } from './utils';           // har doim bundle da

Dynamic import — funksiya kabi, kerak bo'lganda; Promise qaytaradi:

async function pdfYuklab() {
  const { pdfYasash } = await import('./pdf-eksport');   // faqat tugma bosilganda
  pdfYasash(malumot);
}

Vite (va boshqa bundler lar) import() ni ko'rib, pdf-eksport ni alohida faylga (chunk) ajratadi. U faqat import() chaqirilganda tarmoqdan yuklanadi.

dist/assets/index-a1b2c3.js         420 kB    ← asosiy
dist/assets/pdf-eksport-d4e5f6.js   310 kB    ← faqat kerak bo'lganda

Bu React ga bog'liq emas — oddiy JavaScript. Og'ir kutubxonalar uchun juda foydali:

// Markdown faqat muharrir ochilganda
const { marked } = await import('marked');

// Sana kutubxonasi faqat kerakli tilda
const locale = await import(`./locales/${til}.js`);

React.lazy — komponentni kechiktirib yuklash

Komponentlar uchun — lazy:

import { lazy, Suspense } from 'react';

const Grafik = lazy(() => import('./Grafik'));

function Hisobot() {
  const [korsat, setKorsat] = useState(false);

  return (
    <>
      <button onClick={() => setKorsat(true)}>Grafikni ko'rsatish</button>
      {korsat && (
        <Suspense fallback={<p>Grafik yuklanmoqda...</p>}>
          <Grafik />
        </Suspense>
      )}
    </>
  );
}
  • lazy(() => import('./Grafik')) — komponent kodi birinchi render da yuklanadi
  • <Suspense fallback> — yuklanayotganda nima ko'rsatish
  • Fayl default export ga ega bo'lishi kerak

Named export bilan

// Grafik.jsx: export function Grafik() {...}   — default emas
const Grafik = lazy(() =>
  import('./Grafik').then((modul) => ({ default: modul.Grafik }))
);

[!WARNING] lazy() ni komponent ichida chaqirmang — faqat modul darajasida. Komponent ichida har renderda yangi lazy komponent yaratiladi → yangi tur → React uni har safar unmount va mount qiladi (67-dars), state yo'qoladi va kod qayta-qayta "yuklanadi".


Route lar bo'yicha bo'laklash

Eng katta va eng oson yutuq — har bir sahifani alohida chunk qilish (47-darsda ko'rgan edik):

import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
import Layout from './Layout';
import BoshSahifa from './pages/BoshSahifa';          // eng ko'p ochiladigan — statik

const Katalog = lazy(() => import('./pages/Katalog'));
const Mahsulot = lazy(() => import('./pages/Mahsulot'));
const Savat = lazy(() => import('./pages/Savat'));
const Admin = lazy(() => import('./pages/admin/Admin'));

function App() {
  return (
    <Routes>
      <Route element={<Layout />}>
        <Route index element={<BoshSahifa />} />
        <Route path="katalog" element={<Katalog />} />
        <Route path="mahsulot/:id" element={<Mahsulot />} />
        <Route path="savat" element={<Savat />} />
        <Route path="admin/*" element={<Admin />} />
      </Route>
    </Routes>
  );
}
// Layout.jsx
function Layout() {
  return (
    <>
      <Header />
      <Suspense fallback={<SahifaSkeleton />}>
        <Outlet />
      </Suspense>
      <Footer />
    </>
  );
}

Suspense ni Outlet atrofiga qo'ysangiz — sahifa almashganda header va footer joyida qoladi, faqat kontent qismida skeleton ko'rinadi.


Qayerga Suspense qo'yish

// ❌ butun ilova atrofida — har sahifa almashganda butun ekran yo'qoladi
<Suspense fallback={<Spinner />}>
  <App />
</Suspense>

// ✅ o'zgaradigan qism atrofida — qolgan qism ko'rinib turadi
<Header />
<Suspense fallback={<KontentSkeleton />}>
  <Outlet />
</Suspense>

Bir nechta mustaqil lazy qismlar — har biri o'z Suspense i bilan:

<Suspense fallback={<GrafikSkeleton />}>
  <Grafik />
</Suspense>
<Suspense fallback={<JadvalSkeleton />}>
  <Jadval />
</Suspense>

Grafik tayyor bo'lsa — jadvalni kutmasdan ko'rsatiladi.


Oldindan yuklash (prefetch)

Lazy yuklash kamchiligi — foydalanuvchi bosganda kutadi. Buni yashirish mumkin:

const adminImport = () => import('./pages/admin/Admin');
const Admin = lazy(adminImport);

<Link to="/admin" onMouseEnter={adminImport} onFocus={adminImport}>
  Admin panel
</Link>

Sichqoncha havolaga yaqinlashganda kod yuklana boshlaydi. Bosish paytida — allaqachon keshda. import() ning natijasi brauzer tomonidan keshlanadi — ikki marta chaqirish ikki marta yuklamaydi.


Yuklash xatolari

Yangi versiya deploy qilindi, eski chunk lar serverdan o'chirildi. Eski versiyadagi foydalanuvchi sahifani bosadi — import() 404 bilan xato beradi: "Failed to fetch dynamically imported module".

<ErrorBoundary fallback={<p>Sahifani yuklab bo'lmadi. <button onClick={() => location.reload()}>Yangilash</button></p>}>
  <Suspense fallback={<SahifaSkeleton />}>
    <Outlet />
  </Suspense>
</ErrorBoundary>

Suspense faqat kutishni boshqaradi, xatolarni — Error Boundary (79-dars). Lazy komponentlar atrofida doim ikkalasi bo'lsin.


Nimani bo'laklash kerak

✅ Bo'laklash foydali ❌ Foydasiz
Sahifalar (route lar) Har bir kichik komponent
Admin panel, kabinet — faqat ba'zi foydalanuvchilarga Birinchi ekranda darhol ko'rinadigan narsa
Og'ir kutubxonalar: grafiklar, muharrirlar, xaritalar, PDF Kichik util funksiyalar
Modal va dialog oynalar (ochilmasligi mumkin) Header, navigatsiya
Kamdan-kam ishlatiladigan funksiyalar: eksport, import

[!TIP] Juda mayda bo'laklash ham zararli: har bir chunk — alohida tarmoq so'rovi. 100 ta 2 KB li fayl bitta 200 KB li fayldan sekinroq yuklanishi mumkin. Route lar va og'ir kutubxonalar — ko'p hollarda yetarli. Natijani bundle tahlili (72-dars) bilan tekshiring.


Nima uchun shunday?

Nima uchun bundler lar kodni avtomatik bo'laklamaydi — qaysi kod qachon kerakligini o'zi aniqlasa bo'lmaydimi?

Bundler kodning tuzilmasini ko'radi, lekin foydalanuvchining xatti-harakatini bilmaydi. U Admin komponenti import qilinganini ko'radi, lekin uni foydalanuvchilarning 1% i ochishini bilmaydi. U grafiklar kutubxonasini ko'radi, lekin grafik faqat tugma bosilganda chiqishini bilmaydi. Bu qarorlar — mahsulot haqidagi bilim, va uni faqat dasturchi beradi. import() — bu bundler ga aniq signal: "bu yerda chegara bor, bu kod keyinroq kerak bo'ladi". Shundan keyin bundler qolgan ishni — bog'liqliklarni ajratish, umumiy kodni alohida chunk ga chiqarish, fayl nomlariga hash qo'shish — avtomatik bajaradi.


Xulosa

Eslab qoling:

  • Code splitting — kodni chunk larga bo'lib, kerak bo'lganda yuklash; boshlang'ich yuklash tezlashadi
  • await import('./modul') — har qanday JS uchun; og'ir kutubxonalar uchun foydali
  • lazy(() => import('./Komponent')) — default export; faqat modul darajasida
  • <Suspense fallback> — yuklanayotganda; o'zgaradigan qism atrofida, butun ilova emas
  • Eng katta yutuq — route lar bo'yicha bo'laklash
  • Prefetch — onMouseEnter da import() ni chaqirish
  • Yuklash xatolari uchun Error Boundary
  • Juda mayda bo'laklamang; natijani tahlil bilan tekshiring

Keyingi dars

70-dars: Virtualization — react-virtual, katta ro'yxatlar