Что такое React.lazy() и для чего он используется?

👨‍💻 Frontend Developer 🟡 Часто попадается 🎚️ Сложный
#React

Краткий ответ

React.lazy() — это функция, которая позволяет динамически загружать компоненты React. Она реализует паттерн “ленивой загрузки” (lazy loading) и автоматически разделяет код приложения на отдельные бандлы (code splitting).

Основное использование:

// Вместо обычного импорта
import HeavyComponent from './HeavyComponent';
 
// Используем динамический импорт
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
 
// Обязательно оборачиваем в Suspense
function App() {
  return (
    <React.Suspense fallback={<div>Загрузка...</div>}>
      <HeavyComponent />
    </React.Suspense>
  );
}

Полный ответ

React.lazy() появился в React 16.6 и предоставляет встроенный способ для code splitting на уровне компонентов. Это позволяет загружать компоненты только тогда, когда они действительно нужны, уменьшая начальный размер бандла.

Как работает React.lazy()

Базовая механика

// React.lazy принимает функцию, которая возвращает Promise
const LazyComponent = React.lazy(() => import('./LazyComponent'));
 
// import() возвращает Promise, который резолвится в модуль
// Модуль должен экспортировать React компонент по умолчанию

Требования к компонентам

// LazyComponent.jsx
// ✅ Правильно: экспорт по умолчанию
export default function LazyComponent() {
  return <div>Я загружаюсь лениво!</div>;
}
 
// ❌ Неправильно: именованный экспорт
export const LazyComponent = () => {
  return <div>Это не будет работать</div>;
};
 
// Обходной путь для именованных экспортов
const LazyComponent = React.lazy(() =>
  import('./components').then(module => ({
    default: module.NamedComponent
  }))
);

Использование с Suspense

Обязательная обёртка

function App() {
  return (
    <div>
      {/* ❌ Ошибка: LazyComponent должен быть внутри Suspense */}
      <LazyComponent />
      
      {/* ✅ Правильно: используем Suspense */}
      <React.Suspense fallback={<div>Загрузка...</div>}>
        <LazyComponent />
      </React.Suspense>
    </div>
  );
}

Продвинутое использование Suspense

// Можно оборачивать несколько lazy компонентов
function Dashboard() {
  return (
    <React.Suspense fallback={<DashboardSkeleton />}>
      <UserProfile />
      <Analytics />
      <RecentActivity />
    </React.Suspense>
  );
}
 
// Вложенные Suspense для разной гранулярности
function App() {
  return (
    <React.Suspense fallback={<AppLoader />}>
      <Header />
      <React.Suspense fallback={<ContentLoader />}>
        <MainContent />
      </React.Suspense>
      <Footer />
    </React.Suspense>
  );
}

Практические примеры

1. Разделение по маршрутам

import { BrowserRouter, Routes, Route } from 'react-router-dom';
 
// Ленивая загрузка страниц
const Home = React.lazy(() => import('./pages/Home'));
const About = React.lazy(() => import('./pages/About'));
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Profile = React.lazy(() => import('./pages/Profile'));
 
function App() {
  return (
    <BrowserRouter>
      <React.Suspense fallback={<PageLoader />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/profile/:id" element={<Profile />} />
        </Routes>
      </React.Suspense>
    </BrowserRouter>
  );
}

2. Условная загрузка компонентов

const AdminPanel = React.lazy(() => import('./AdminPanel'));
 
function App() {
  const [user, setUser] = useState(null);
  
  return (
    <div>
      {user?.isAdmin && (
        <React.Suspense fallback={<div>Загрузка панели...</div>}>
          <AdminPanel user={user} />
        </React.Suspense>
      )}
    </div>
  );
}

3. Загрузка тяжёлых библиотек

// Компонент с тяжёлой библиотекой для графиков
const ChartComponent = React.lazy(() => import('./ChartComponent'));
 
function Analytics() {
  const [showChart, setShowChart] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowChart(true)}>
        Показать график
      </button>
      
      {showChart && (
        <React.Suspense fallback={<ChartSkeleton />}>
          <ChartComponent data={analyticsData} />
        </React.Suspense>
      )}
    </div>
  );
}

Обработка ошибок

Error Boundaries с lazy компонентами

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    console.error('Ошибка загрузки компонента:', error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return <div>Не удалось загрузить компонент</div>;
    }
    
    return this.props.children;
  }
}
 
// Использование
function App() {
  return (
    <ErrorBoundary>
      <React.Suspense fallback={<Loader />}>
        <LazyComponent />
      </React.Suspense>
    </ErrorBoundary>
  );
}

Retry механизм

function LazyComponentWithRetry(componentPath) {
  return React.lazy(() =>
    import(componentPath).catch(() => {
      // Перезагрузка страницы при ошибке загрузки чанка
      window.location.reload();
      // Или повторная попытка
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(import(componentPath));
        }, 1500);
      });
    })
  );
}
 
const MyComponent = LazyComponentWithRetry('./MyComponent');

Продвинутые паттерны

1. Предзагрузка компонентов

// Функция для предзагрузки
const preloadComponent = (component) => {
  component._init();
};
 
// Создаём lazy компонент
const HeavyModal = React.lazy(() => import('./HeavyModal'));
 
// Предзагружаем при наведении
function App() {
  const [showModal, setShowModal] = useState(false);
  
  return (
    <div>
      <button
        onMouseEnter={() => preloadComponent(HeavyModal)}
        onClick={() => setShowModal(true)}
      >
        Открыть модалку
      </button>
      
      {showModal && (
        <React.Suspense fallback={<ModalLoader />}>
          <HeavyModal onClose={() => setShowModal(false)} />
        </React.Suspense>
      )}
    </div>
  );
}

2. Прогрессивная загрузка

// Компонент с прогресс-баром
function ProgressiveSuspense({ children }) {
  const [progress, setProgress] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      setProgress(prev => Math.min(prev + 10, 90));
    }, 100);
    
    return () => clearInterval(timer);
  }, []);
  
  return (
    <React.Suspense
      fallback={
        <div>
          <div>Загрузка... {progress}%</div>
          <ProgressBar value={progress} />
        </div>
      }
    >
      {children}
    </React.Suspense>
  );
}

3. Динамические импорты с параметрами

// Функция для динамической загрузки локалей
const loadLocale = (locale) => {
  return React.lazy(() =>
    import(`./locales/${locale}.js`).then(module => ({
      default: module.LocaleProvider
    }))
  );
};
 
function App() {
  const [locale, setLocale] = useState('ru');
  const LocaleProvider = useMemo(() => loadLocale(locale), [locale]);
  
  return (
    <React.Suspense fallback={<div>Загрузка языка...</div>}>
      <LocaleProvider>
        <MainApp />
      </LocaleProvider>
    </React.Suspense>
  );
}

Оптимизация производительности

1. Группировка lazy компонентов

// Вместо множества мелких lazy импортов
const Button = React.lazy(() => import('./Button'));
const Input = React.lazy(() => import('./Input'));
const Select = React.lazy(() => import('./Select'));
 
// Лучше сгруппировать в один модуль
const FormComponents = React.lazy(() => import('./FormComponents'));
// FormComponents экспортирует { Button, Input, Select }

2. Стратегии разделения кода

// По функциональности
const AdminFeatures = React.lazy(() => import('./features/admin'));
const UserFeatures = React.lazy(() => import('./features/user'));
 
// По размеру бандла
const LargeTable = React.lazy(() => 
  import(/* webpackChunkName: "large-table" */ './LargeTable')
);
 
// По приоритету загрузки
const CriticalComponent = React.lazy(() =>
  import(/* webpackPreload: true */ './CriticalComponent')
);
 
const OptionalComponent = React.lazy(() =>
  import(/* webpackPrefetch: true */ './OptionalComponent')
);

Ограничения и подводные камни

1. Только для default exports

// ❌ Не работает напрямую с именованными экспортами
const { Component } = React.lazy(() => import('./module'));
 
// ✅ Нужно преобразовать
const Component = React.lazy(() =>
  import('./module').then(module => ({
    default: module.Component
  }))
);

2. Не работает с серверным рендерингом

// Для SSR используйте loadable-components или похожие решения
import loadable from '@loadable/component';
 
const LazyComponent = loadable(() => import('./Component'));

3. Необходимость в Suspense

// ❌ Забыли Suspense = ошибка
<LazyComponent />
 
// ✅ Всегда оборачивайте в Suspense
<React.Suspense fallback={<div>Loading...</div>}>
  <LazyComponent />
</React.Suspense>

Лучшие практики

  1. Разделяйте по маршрутам — самый эффективный способ
  2. Группируйте связанные компоненты — избегайте слишком мелкой грануляции
  3. Используйте осмысленные fallback — показывайте скелетоны вместо спиннеров
  4. Предзагружайте критические компоненты — улучшайте UX
  5. Обрабатывайте ошибки загрузки — сеть может быть нестабильной
  6. Тестируйте с медленным интернетом — проверяйте реальный опыт пользователей

React.lazy() — мощный инструмент для оптимизации производительности React-приложений, который при правильном использовании значительно улучшает время начальной загрузки.


Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪