React.lazy() всегда должен использоваться вместе с Suspense — это обязательное требование. Suspense предоставляет fallback UI на время загрузки lazy-компонента. Без Suspense приложение выбросит ошибку.
Правильное использование:
// Создаем lazy компонент
const LazyComponent = React.lazy(() => import('./LazyComponent'));
// Обязательно оборачиваем в Suspense
function App() {
return (
<React.Suspense fallback={<div>Загрузка...</div>}>
<LazyComponent />
</React.Suspense>
);
}React.lazy() и Suspense работают в паре для реализации code splitting. Suspense — это механизм, который “приостанавливает” рендеринг компонента до тех пор, пока не будут загружены необходимые ресурсы.
// ❌ БЕЗ Suspense - приложение упадет с ошибкой
function App() {
const LazyComponent = React.lazy(() => import('./LazyComponent'));
return (
<div>
<h1>Мое приложение</h1>
{/* Ошибка: A React component suspended while rendering */}
<LazyComponent />
</div>
);
}// React.lazy возвращает специальный объект
const LazyComponent = React.lazy(() => import('./Component'));
// Этот объект при первом рендере "выбрасывает" Promise
// Suspense перехватывает этот Promise и показывает fallback
// Без Suspense Promise не перехватывается = ошибкаconst Header = React.lazy(() => import('./Header'));
const Content = React.lazy(() => import('./Content'));
const Footer = React.lazy(() => import('./Footer'));
function App() {
return (
// ✅ Можно оборачивать несколько компонентов
<React.Suspense fallback={<div>Загрузка приложения...</div>}>
<Header />
<Content />
<Footer />
</React.Suspense>
);
}function Dashboard() {
// Все три компонента начнут загружаться параллельно
const Stats = React.lazy(() => import('./Stats'));
const Charts = React.lazy(() => import('./Charts'));
const Table = React.lazy(() => import('./Table'));
return (
<React.Suspense fallback={<DashboardSkeleton />}>
{/* Fallback будет показан пока ВСЕ компоненты не загрузятся */}
<Stats />
<Charts />
<Table />
</React.Suspense>
);
}function App() {
return (
<div>
{/* Каждый компонент загружается независимо */}
<React.Suspense fallback={<HeaderSkeleton />}>
<Header />
</React.Suspense>
<React.Suspense fallback={<MainSkeleton />}>
<MainContent />
</React.Suspense>
<React.Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</React.Suspense>
</div>
);
}function App() {
return (
// Внешний Suspense для критических компонентов
<React.Suspense fallback={<AppLoader />}>
<Layout>
<Header />
{/* Внутренний Suspense для контента */}
<React.Suspense fallback={<ContentLoader />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</React.Suspense>
<Footer />
</Layout>
</React.Suspense>
);
}// ❌ Водопад - компоненты загружаются последовательно
function WaterfallLoading() {
const [showSecond, setShowSecond] = useState(false);
return (
<>
<React.Suspense fallback={<div>Загрузка первого...</div>}>
<FirstComponent onLoad={() => setShowSecond(true)} />
</React.Suspense>
{showSecond && (
<React.Suspense fallback={<div>Загрузка второго...</div>}>
<SecondComponent />
</React.Suspense>
)}
</>
);
}
// ✅ Параллельная загрузка - эффективнее
function ParallelLoading() {
return (
<React.Suspense fallback={<div>Загрузка компонентов...</div>}>
<FirstComponent />
<SecondComponent />
</React.Suspense>
);
}// Если компонент не загрузится, Suspense не поможет
function App() {
return (
<React.Suspense fallback={<div>Загрузка...</div>}>
{/* Если загрузка провалится - увидим белый экран */}
<LazyComponent />
</React.Suspense>
);
}class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// Логирование ошибки
console.error('Ошибка загрузки chunk:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h2>Не удалось загрузить компонент</h2>
<button onClick={() => window.location.reload()}>
Перезагрузить страницу
</button>
</div>
);
}
return this.props.children;
}
}
// Использование
function App() {
return (
<ErrorBoundary>
<React.Suspense fallback={<Loader />}>
<LazyComponent />
</React.Suspense>
</ErrorBoundary>
);
}function createLazyWithRetry(componentImport) {
return React.lazy(() =>
componentImport().catch((error) => {
// Проверяем, является ли это ошибкой загрузки chunk
if (error.name === 'ChunkLoadError') {
// Пробуем загрузить еще раз
return new Promise((resolve) => {
setTimeout(() => {
resolve(componentImport());
}, 1500);
});
}
throw error;
})
);
}
// Использование
const RobustLazyComponent = createLazyWithRetry(
() => import('./HeavyComponent')
);function RetryableErrorBoundary({ children }) {
const [hasError, setHasError] = useState(false);
const [retryCount, setRetryCount] = useState(0);
const resetError = () => {
setHasError(false);
setRetryCount(prev => prev + 1);
};
if (hasError) {
return (
<div className="error-container">
<h3>Ошибка загрузки</h3>
<p>Не удалось загрузить необходимые ресурсы</p>
<button onClick={resetError}>
Попробовать снова ({retryCount})
</button>
</div>
);
}
return (
<ErrorBoundary onError={() => setHasError(true)}>
{/* Force re-mount on retry */}
<React.Fragment key={retryCount}>
{children}
</React.Fragment>
</ErrorBoundary>
);
}function DelayedSuspense({ delay = 300, fallback, children }) {
const [showFallback, setShowFallback] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
setShowFallback(true);
}, delay);
return () => clearTimeout(timer);
}, [delay]);
return (
<React.Suspense fallback={showFallback ? fallback : null}>
{children}
</React.Suspense>
);
}
// Использование - fallback появится только если загрузка > 300ms
<DelayedSuspense fallback={<Spinner />}>
<LazyComponent />
</DelayedSuspense>function ProgressiveFallback() {
const [stage, setStage] = useState(0);
useEffect(() => {
const timers = [
setTimeout(() => setStage(1), 500), // "Загрузка..."
setTimeout(() => setStage(2), 2000), // "Почти готово..."
setTimeout(() => setStage(3), 5000), // "Это занимает больше времени..."
];
return () => timers.forEach(clearTimeout);
}, []);
const messages = [
"Загрузка...",
"Почти готово...",
"Это занимает больше времени, чем обычно...",
"Пожалуйста, проверьте соединение..."
];
return (
<div className="progressive-loader">
<Spinner />
<p>{messages[stage]}</p>
</div>
);
}// ✅ Хорошо - логически связанные компоненты
<React.Suspense fallback={<DashboardSkeleton />}>
<DashboardHeader />
<DashboardStats />
<DashboardCharts />
</React.Suspense>
// ❌ Плохо - несвязанные компоненты
<React.Suspense fallback={<div>Loading...</div>}>
<UserProfile />
<WeatherWidget />
<StockTicker />
</React.Suspense>// ✅ Хорошо - скелетон соответствует контенту
<React.Suspense fallback={<ArticleSkeleton />}>
<Article />
</React.Suspense>
// ❌ Плохо - generic спиннер для всего
<React.Suspense fallback={<Spinner />}>
<ComplexDashboard />
</React.Suspense>function App() {
return (
<>
{/* Критические компоненты без Suspense */}
<NavigationBar />
{/* Основной контент с Suspense */}
<React.Suspense fallback={<MainSkeleton />}>
<Routes>
<Route path="/*" element={<MainApp />} />
</Routes>
</React.Suspense>
{/* Некритические компоненты с отдельным Suspense */}
<React.Suspense fallback={null}>
<Analytics />
<ChatWidget />
</React.Suspense>
</>
);
}React.lazy() и Suspense — мощная комбинация для оптимизации загрузки, но требует правильного использования для обеспечения хорошего пользовательского опыта.
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪