Podczas długiej międzygalaktycznej podróży, nie potrzebujemy od razu ładować całego wyposażenia na statek kosmiczny. Podobnie w aplikacjach React, szczególnie tych większych, nie potrzebujemy ładować wszystkich komponentów od razu podczas inicjalizacji aplikacji. Oto gdzie przychodzi nam z pomocą leniwe ładowanie (lazy loading).
Lazy loading to technika optymalizacji wydajności, która polega na ładowaniu komponentów, modułów lub zasobów tylko wtedy, gdy są faktycznie potrzebne, zamiast ładować je z góry podczas inicjalizacji aplikacji. W React, realizujemy to za pomocą funkcji
React.lazy() w połączeniu z Suspense.Aby zaimplementować lazy loading komponentu, zamiast standardowego importowania komponentu:
1import HeavyDashboard from './HeavyDashboard';Używamy funkcji
React.lazy() z dynamicznym importem:1import React, { lazy, Suspense } from 'react';
2
3// Leniwe ładowanie komponentu
4const HeavyDashboard = lazy(() => import('./HeavyDashboard'));
5
6function App() {
7 return (
8 <div>
9 <h1>Centrum Kontroli Lotu</h1>
10
11 {/* Suspense zapewnia komponent zastępczy podczas ładowania */}
12 <Suspense fallback={<div>Ładowanie panelu sterowania...</div>}>
13 <HeavyDashboard />
14 </Suspense>
15 </div>
16 );
17}W tym przykładzie:
lazy() przyjmuje funkcję, która musi wywołać dynamiczny import()Suspense wyświetla fallback (komponent zastępczy) podczas ładowania leniwego komponentuHeavyDashboard załaduje się dopiero wtedy, gdy będzie renderowanyW aplikacjach wykorzystujących React Router, możemy leniwie ładować komponenty dla poszczególnych tras. Dzięki temu początkowy bundle aplikacji będzie mniejszy, a użytkownik będzie pobierać kod tylko dla tych części aplikacji, które odwiedza:
1import React, { lazy, Suspense } from 'react';
2import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
3
4// Komponenty ładowane leniwie
5const MissionControl = lazy(() => import('./MissionControl'));
6const SpaceshipSettings = lazy(() => import('./SpaceshipSettings'));
7const GalacticMap = lazy(() => import('./GalacticMap'));
8const CrewQuarters = lazy(() => import('./CrewQuarters'));
9
10// Komponent ładowany zawsze (mały i często używany)
11import NavigationBar from './NavigationBar';
12
13function App() {
14 return (
15 <Router>
16 <NavigationBar />
17
18 <Suspense fallback={<div className="loading-screen">Przygotowanie modułu...</div>}>
19 <Routes>
20 <Route path="/" element={<HomePage />} />
21 <Route path="/mission-control" element={<MissionControl />} />
22 <Route path="/spaceship-settings" element={<SpaceshipSettings />} />
23 <Route path="/galactic-map" element={<GalacticMap />} />
24 <Route path="/crew-quarters" element={<CrewQuarters />} />
25 </Routes>
26 </Suspense>
27 </Router>
28 );
29}W tym przykładzie tylko komponenty dla aktualnie odwiedzanej trasy będą ładowane. Możemy również dodać indywidualne komponenty
Suspense dla każdej trasy, jeśli chcemy różne wskaźniki ładowania:1<Routes>
2 <Route path="/" element={<HomePage />} />
3 <Route
4 path="/mission-control"
5 element={
6 <Suspense fallback={<div>Inicjalizacja panelu kontroli misji...</div>}>
7 <MissionControl />
8 </Suspense>
9 }
10 />
11 <Route
12 path="/galactic-map"
13 element={
14 <Suspense fallback={<div>Ładowanie mapy galaktycznej...</div>}>
15 <GalacticMap />
16 </Suspense>
17 }
18 />
19</Routes>Czasami wiemy z dużym prawdopodobieństwem, że użytkownik odwiedzi określoną trasę. Możemy wtedy wstępnie załadować komponent, zanim użytkownik faktycznie kliknie w link:
1import { lazy } from 'react';
2
3// Deklaracja leniwego komponentu
4const GalacticMap = lazy(() => import('./GalacticMap'));
5
6// Komponent nawigacyjny z prefetchingiem
7function NavigationLink({ to, children }) {
8 // Funkcja do wstępnego ładowania
9 const prefetchComponent = () => {
10 // Dynamiczny import jako Promise - rozpoczyna ładowanie
11 import('./GalacticMap');
12 };
13
14 return (
15 <Link
16 to={to}
17 onMouseOver={prefetchComponent} // Rozpocznij ładowanie po najechaniu myszą
18 onFocus={prefetchComponent} // Dla dostępności, ładowanie po fokusie
19 >
20 {children}
21 </Link>
22 );
23}
24
25// Użycie w komponencie
26function Navigation() {
27 return (
28 <nav>
29 <NavigationLink to="/galactic-map">Mapa Galaktyczna</NavigationLink>
30 </nav>
31 );
32}Możemy grupować powiązane ze sobą lazy-loaded komponenty, aby zmniejszyć liczbę oddzielnych żądań sieciowych:
1// Grupowanie komponentów związanych z kokpitem
2const CockpitModule = lazy(() => import('./modules/CockpitModule'));
3
4// Używanie zgrupowanego modułu w routingu
5<Route path="/cockpit/*" element={<CockpitModule />} />
6
7// W CockpitModule.js mamy wewnętrzne route'y
8function CockpitModule() {
9 return (
10 <Routes>
11 <Route path="/" element={<CockpitOverview />} />
12 <Route path="/navigation" element={<NavigationControls />} />
13 <Route path="/engines" element={<EngineControls />} />
14 <Route path="/life-support" element={<LifeSupportSystems />} />
15 </Routes>
16 );
17}W tym podejściu, wszystkie komponenty kokpitu zostaną pobrane jako jeden chunk kodu, gdy użytkownik wejdzie na dowolną trasę
/cockpit/*.W kosmosie zawsze musimy być przygotowani na nieprzewidziane sytuacje. W przypadku lazy loadingu, może się zdarzyć, że próba ładowania komponentu się nie powiedzie (np. z powodu problemów z siecią):
1import React, { lazy, Suspense } from 'react';
2import { ErrorBoundary } from 'react-error-boundary';
3
4const GalacticMap = lazy(() => import('./GalacticMap'));
5
6function ErrorFallback({ error, resetErrorBoundary }) {
7 return (
8 <div className="error-container">
9 <h2>Napotkano problem podczas ładowania modułu</h2>
10 <p>Błąd: {error.message}</p>
11 <button onClick={resetErrorBoundary}>
12 Spróbuj ponownie
13 </button>
14 </div>
15 );
16}
17
18function MapSection() {
19 return (
20 <ErrorBoundary
21 FallbackComponent={ErrorFallback}
22 onReset={() => {
23 // Opcjonalna logika resetowania stanu
24 }}
25 >
26 <Suspense fallback={<div>Ładowanie mapy galaktycznej...</div>}>
27 <GalacticMap />
28 </Suspense>
29 </ErrorBoundary>
30 );
31}Lazy loading jest szczególnie przydatny w następujących przypadkach:
Duże aplikacje z wieloma komponentami, gdzie użytkownik zazwyczaj korzysta tylko z niewielkiej części funkcjonalności podczas jednej sesji
Rzadko używane funkcje - na przykład, panel administratora, który jest używany tylko przez kilku użytkowników
Ciężkie zależności - komponenty, które wykorzystują duże biblioteki (np. edytory tekstu, mapy, wykresy)
Alternatywne ścieżki użytkownika - gdy różni użytkownicy korzystają z różnych części aplikacji w zależności od ich roli
Suspense wyżej w drzewie komponentówZamiast owijać każdy lazy-loaded komponent w osobny
Suspense, często lepiej jest umieścić jeden Suspense wyżej w drzewie:1function App() {
2 return (
3 <Suspense fallback={<GlobalLoadingSpinner />}>
4 <Router>
5 <Routes>
6 {/* Lazy-loaded routes */}
7 </Routes>
8 </Router>
9 </Suspense>
10 );
11}Dziel kod wzdłuż naturalnych granic aplikacji:
Zbyt drobny podział kodu może prowadzić do wielu małych żądań sieciowych, co może być mniej wydajne niż kilka większych. Znajdź złoty środek.
Upewnij się, że
fallback w Suspense jest wizualnie spójny z resztą aplikacji i daje użytkownikowi wyraźną informację o stanie ładowania.Lazy loading może działać świetnie na szybkich połączeniach developerskich, ale zawsze testuj jak wygląda doświadczenie użytkownika na wolniejszych połączeniach.
Oto przykład bardziej rozbudowanej aplikacji kosmicznej z lazy loadingiem:
1import React, { lazy, Suspense } from 'react';
2import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
3import { ErrorBoundary } from 'react-error-boundary';
4
5// Komponenty ładowane zawsze
6import MainLayout from './layouts/MainLayout';
7import LoadingScreen from './components/LoadingScreen';
8import ErrorFallback from './components/ErrorFallback';
9
10// Lazy-loaded pages
11const HomePage = lazy(() => import('./pages/HomePage'));
12const Dashboard = lazy(() => import('./pages/Dashboard'));
13const NavigationModule = lazy(() => import('./modules/NavigationModule'));
14const LifeSupportModule = lazy(() => import('./modules/LifeSupportModule'));
15const CommunicationModule = lazy(() => import('./modules/CommunicationModule'));
16const CrewModule = lazy(() => import('./modules/CrewModule'));
17const SettingsPage = lazy(() => import('./pages/SettingsPage'));
18const NotFoundPage = lazy(() => import('./pages/NotFoundPage'));
19
20function App() {
21 return (
22 <Router>
23 <ErrorBoundary FallbackComponent={ErrorFallback}>
24 <MainLayout>
25 <Suspense fallback={<LoadingScreen />}>
26 <Routes>
27 <Route path="/" element={<HomePage />} />
28 <Route path="/dashboard" element={<Dashboard />} />
29
30 {/* Zagnieżdżone moduły z własnymi trasami */}
31 <Route path="/navigation/*" element={<NavigationModule />} />
32 <Route path="/life-support/*" element={<LifeSupportModule />} />
33 <Route path="/communication/*" element={<CommunicationModule />} />
34 <Route path="/crew/*" element={<CrewModule />} />
35
36 <Route path="/settings" element={<SettingsPage />} />
37 <Route path="/404" element={<NotFoundPage />} />
38 <Route path="*" element={<Navigate to="/404" replace />} />
39 </Routes>
40 </Suspense>
41 </MainLayout>
42 </ErrorBoundary>
43 </Router>
44 );
45}
46
47export default App;Lazy loading to potężna technika optymalizacji React aplikacji, która pozwala:
Jest to jak inteligentne zarządzanie zasobami na statku kosmicznym - zamiast zabierać wszystko na początku podróży, transportujemy tylko to, co jest aktualnie potrzebne, oszczędzając paliwo i przyspieszając start.