Lazy Loading — Route larni bo'laklash, Suspense bilan

~15 daq · React · Routing

Muammodan boshlaylik

Ilovangiz o'sib bordi: 20 ta sahifa, har birida o'z komponentlari, kutubxonalari. Foydalanuvchi birinchi marta saytga kirdi.

Brauzer barcha sahifalarni — foydalanuvchi hech qachon ko'rmaydigan sahifalarni ham — bir vaqtda yuklab oladi. Bundle hajmi — 2MB. Yuklash vaqti — 5-8 soniya. Foydalanuvchi yarim yo'lda saytni yopdi.

Muammo: hamma narsani birdaniga yuklash.

Yechim: foydalanuvchi faqat ko'rmoqchi bo'lgan sahifani yuklaydi. /bosh-sahifa ga kirsa — faqat bosh sahifa kodi yuklanadi. /mahsulotlar ga o'tsa — o'shanda mahsulotlar kodi yuklanadi.

Bu — Lazy Loading (dangasa yuklash).


Lazy Loading qanday ishlaydi?

Oddiy import — barcha fayllar bir bundleda:

import Dashboard from './pages/Dashboard';
import Mahsulotlar from './pages/Mahsulotlar';
import Sozlamalar from './pages/Sozlamalar';
// Hammasi bitta js faylga yig'iladi — katta bundle

Lazy import — har sahifa alohida chunk (bo'lak):

const Dashboard = lazy(() => import('./pages/Dashboard'));
const Mahsulotlar = lazy(() => import('./pages/Mahsulotlar'));
const Sozlamalar = lazy(() => import('./pages/Sozlamalar'));
// Har biri alohida js fayl — kerak bo'lganda yuklanadi

Vite va webpack bu lazy() chaqiriqlarini ko'rib, har bir import uchun alohida fayl (chunk) yaratadi. Foydalanuvchi o'sha Route ga o'tganda — o'sha chunk yuklanadi.


Hayotiy o'xshatma

Restoran menyusi. Oddiy usul: barcha taomlarni darhol tayyorlab qo'yish — isrof. Yaxshi usul: buyurtma kelganda tayyorlash.

Lazy Loading ham shunday: sahifa so'ralganda kodi yuklanadi, oldin emas. Birinchi yuklash tez bo'ladi, keyin har sahifa birinchi marta ochilganda biroz kutiladi — lekin ikkinchi marta tezda ochiladi (keshi ishga tushadi).


React.lazy va Suspense

React.lazy — dinamik import ni komponentga aylantiradi. Suspense — komponent yuklanayotganda nima ko'rsatilishini belgilaydi.

import { lazy, Suspense } from 'react';

// Lazy import
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Mahsulotlar = lazy(() => import('./pages/Mahsulotlar'));

function App() {
  return (
    <Suspense fallback={<div>Yuklanmoqda...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/mahsulotlar" element={<Mahsulotlar />} />
      </Routes>
    </Suspense>
  );
}

fallback — komponent yuklanayotganda ko'rsatiladigan UI. Chunk yuklanib bo'lgach, fallback yo'qoladi va haqiqiy komponent ko'rsatiladi.


To'liq misol — barcha Route lar lazy

// App.jsx
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
import ProtectedRoute from './components/ProtectedRoute';
import YuklanishEkrani from './components/YuklanishEkrani';

// Ochiq sahifalar
const Bosh = lazy(() => import('./pages/Bosh'));
const Kirish = lazy(() => import('./pages/Kirish'));
const TopilmadI = lazy(() => import('./pages/TopilmadI'));

// Himoyalangan sahifalar
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profil = lazy(() => import('./pages/Profil'));
const Mahsulotlar = lazy(() => import('./pages/Mahsulotlar'));
const MahsulotDetali = lazy(() => import('./pages/MahsulotDetali'));
const Sozlamalar = lazy(() => import('./pages/Sozlamalar'));

function App() {
  return (
    <Suspense fallback={<YuklanishEkrani />}>  {/* ← bir marta, yuqorida */}
      <Routes>
        <Route path="/" element={<Bosh />} />
        <Route path="/kirish" element={<Kirish />} />

        <Route element={<ProtectedRoute />}>
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/profil" element={<Profil />} />
          <Route path="/mahsulotlar" element={<Mahsulotlar />} />
          <Route path="/mahsulot/:id" element={<MahsulotDetali />} />
          <Route path="/sozlamalar" element={<Sozlamalar />} />
        </Route>

        <Route path="*" element={<TopilmadI />} />
      </Routes>
    </Suspense>
  );
}

export default App;

Yaxshi yuklanish ekrani

Oddiy <div>Yuklanmoqda...</div> ishlatmang — foydalanuvchi sahifa ko'rinishini kutayotganda bo'sh ekran yoki matn ko'radi. Skeleton UI — sahifaning sxematik ko'rinishi — ancha yaxshiroq:

// components/YuklanishEkrani.jsx
function YuklanishEkrani() {
  return (
    <div style={{ padding: '24px' }}>
      {/* Sarlavha skeleton */}
      <div style={{
        width: '200px', height: '28px',
        background: '#e2e8f0', borderRadius: '4px',
        marginBottom: '16px', animation: 'pulse 1.5s infinite'
      }} />

      {/* Matn satrlari skeleton */}
      {[100, 80, 90, 60].map((kenglik, i) => (
        <div key={i} style={{
          width: `${kenglik}%`, height: '16px',
          background: '#e2e8f0', borderRadius: '4px',
          marginBottom: '8px', animation: 'pulse 1.5s infinite'
        }} />
      ))}
    </div>
  );
}

export default YuklanishEkrani;

Har Route uchun alohida Suspense

Bir Suspense barcha Route larni o'rasa — istalgan Route yuklanayotganda butun sahifa fallback ga almashadi.

Har Route uchun alohida Suspense — faqat o'sha qism yuklanish ko'rsatadi, qolgan UI o'zgarmasdan turadi:

<Routes>
  <Route path="/" element={<Bosh />} />

  <Route
    path="/dashboard"
    element={
      <Suspense fallback={<DashboardSkeleton />}>
        <Dashboard />
      </Suspense>
    }
  />

  <Route
    path="/mahsulotlar"
    element={
      <Suspense fallback={<MahsulotlarSkeleton />}>
        <Mahsulotlar />
      </Suspense>
    }
  />
</Routes>

Bu murakkabroq, lekin UX (foydalanuvchi tajribasi) yaxshiroq. Oddiy loyihalarda bitta Suspense yetarli.


Qaysi sahifalarni lazy qilish kerak?

Hammani lazy qilish shart emas. Qoidalar:

Lazy qiling:

  • Katta va kam tashriflangan sahifalar — Admin paneli, Hisobotlar
  • Og'ir kutubxonalar ishlatadigan sahifalar — grafik, xarita, editor
  • Himoyalangan sahifalar — login qilmagan ko'rmaydi

Lazy qilmang:

  • Bosh sahifa — foydalanuvchi darhol ko'radi
  • Login sahifasi — birinchi ochiladi
  • Kichik, tez yuklanadigan komponentlar — ortiqcha chunk yaratish ham yomon

Preloading — oldindan yuklash

Foydalanuvchi havola ustiga sichqoncha olib kelganda — hali bosmagan, lekin bosishi ehtimoli katta. Shu vaqtda chunk ni yuklashni boshlash mumkin:

const Mahsulotlar = lazy(() => import('./pages/Mahsulotlar'));

// Oldindan yuklash funksiyasi
function mahsulotlarniOldinYukla() {
  import('./pages/Mahsulotlar'); // ← React.lazy dan mustaqil import
}

function Nav() {
  return (
    <nav>
      <Link
        to="/mahsulotlar"
        onMouseEnter={mahsulotlarniOldinYukla} // ← hover da yuklash boshlanadi
      >
        Mahsulotlar
      </Link>
    </nav>
  );
}

Foydalanuvchi havolaga bosguncha chunk yuklanib bo'ladi — sahifa bir zumda ochiladi.


Tez-tez uchraydigan xatolar

1. Suspense siz lazy ishlatish

// ❌ Xato — Suspense yo'q
const Dashboard = lazy(() => import('./pages/Dashboard'));

function App() {
  return (
    <Routes>
      <Route path="/dashboard" element={<Dashboard />} />
    </Routes>
    // Suspense yo'q — React xato chiqaradi:
    // "A React component suspended while rendering, but no fallback UI was specified"
  );
}

// ✅ To'g'ri — Suspense o'rab turadi
function App() {
  return (
    <Suspense fallback={<div>Yuklanmoqda...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Suspense>
  );
}

Sababi: lazy bilan yuklangan komponent yuklangunga qadar React "toʻxtatilgan" (suspended) holatda bo'ladi. Suspense bo'lmasa bu holatni kim boshqarishi noma'lum — React xato chiqaradi.


2. lazy ni komponent ichida chaqirish

// ❌ Xato — har render da yangi lazy yaratiladi
function App() {
  const Dashboard = lazy(() => import('./pages/Dashboard')); // ← ichida!
  return <Routes>...</Routes>;
}

// ✅ To'g'ri — komponent tashqarisida, modul darajasida
const Dashboard = lazy(() => import('./pages/Dashboard'));

function App() {
  return <Routes>...</Routes>;
}

Sababi: lazy komponent ichida bo'lsa, har render da yangi lazy obyekti yaratiladi. React "bu yangi komponent" deb biladi va har safar qayta mount qiladi — kesh ishlamaydi, cheksiz yuklash davom etadi.


3. Default export yo'q

// ❌ Xato — default export yo'q
// pages/Dashboard.jsx
export function Dashboard() { ... } // named export

// App.jsx
const Dashboard = lazy(() => import('./pages/Dashboard'));
// Xato: element type is invalid
// ✅ To'g'ri — default export bor
// pages/Dashboard.jsx
function Dashboard() { ... }
export default Dashboard; // ← default export shart

// Yoki named export ni o'rab olish:
const Dashboard = lazy(() =>
  import('./pages/Dashboard').then((m) => ({ default: m.Dashboard }))
);

Sababi: React.lazy faqat default export li modullarni qo'llab-quvvatlaydi. Named export bo'lsa — .then() bilan default ga o'girish kerak.


4. Tarmoq xatosini e'tiborsiz qoldirish

Foydalanuvchi chunk yuklanayotganda tarmoq uzilsa — xato ekrani ko'rsatish kerak. Buning uchun ErrorBoundary ishlatiladi (79-darsda batafsil):

// Oddiy yechim — lazy import da xatoni ushlash
const Dashboard = lazy(() =>
  import('./pages/Dashboard').catch(() => ({
    default: () => (
      <div>
        Sahifa yuklanmadi.{' '}
        <button onClick={() => window.location.reload()}>
          Qayta urinish
        </button>
      </div>
    ),
  }))
);

Xulosa

  • Lazy Loading — sahifa kodi faqat kerak bo'lganda yuklanadi
  • React.lazy(() => import('./Sahifa')) — dinamik import
  • <Suspense fallback={...}> — yuklash vaqtida ko'rsatiladigan UI
  • lazy modul darajasida — komponent ichida emas
  • Komponentlar default export bilan eksport qilinishi shart
  • Bosh sahifa va login sahifasini lazy qilmang
  • Preloading — onMouseEnter da oldindan yuklash
  • Har Route uchun alohida Suspense — finer-grained UX

Keyingi dars: 48-dars: Fetch va Axios — React da HTTP so'rovlar