Функция lazy в React Router

В современных приложениях на React часто возникает необходимость разделять код на части, чтобы оптимизировать загрузку страниц и снизить время первого рендера. Для этих целей React Router предоставляет функцию lazy, которая позволяет динамически загружать компоненты маршрутов только по необходимости.


Основы работы lazy

Функция lazy используется для ленивой загрузки компонентов маршрутов. Она интегрируется с динамическим импортом из ES-модулей:

import { lazy } from "react";
import { createBrowserRouter } from "react-router-dom";

const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));

const router = createBrowserRouter([
  { path: "/", element: <Home /> },
  { path: "/about", element: <About /> },
]);

Здесь lazy(() => import("./pages/Home")) создаёт компонент, который будет загружен только при необходимости. На этапе сборки Webpack формирует отдельные чанки для этих компонентов, что снижает размер основного бандла.


Интеграция с Suspense

Так как lazy возвращает компонент, загрузка которого асинхронна, необходимо обернуть маршруты или отдельные ленивые компоненты в <Suspense>:

import { Suspense } from "react";
import { RouterProvider } from "react-router-dom";

<Suspense fallback={<div>Загрузка...</div>}>
  <RouterProvider router={router} />
</Suspense>

fallback отображается до того момента, пока ленивый компонент полностью не загрузится. Это может быть простой индикатор загрузки, спиннер или любая другая UI-компонента.


Ленивые маршруты с вложенной навигацией

React Router поддерживает ленивые компоненты в любой части дерева маршрутов, включая вложенные маршруты:

const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));

const router = createBrowserRouter([
  {
    path: "/dashboard",
    element: <Dashboard />,
    children: [
      { path: "settings", element: <Settings /> },
    ],
  },
]);

Такой подход позволяет загружать каждый уровень маршрутов отдельно, уменьшая начальный объём кода и ускоряя время отклика.


Обработка ошибок при загрузке

При использовании ленивых компонентов возможны ошибки загрузки, например из-за сетевых проблем. Для их обработки можно использовать ErrorBoundary:

import { lazy } from "react";
import { Suspense } from "react";

const Profile = lazy(() => import("./pages/Profile"));

function ProfileWrapper() {
  return (
    <ErrorBoundary fallback={<div>Ошибка загрузки профиля</div>}>
      <Suspense fallback={<div>Загрузка профиля...</div>}>
        <Profile />
      </Suspense>
    </ErrorBoundary>
  );
}

ErrorBoundary гарантирует, что сбой в ленивой загрузке не приведёт к падению всего приложения.


Совместное использование с Data Loading

React Router версии 6.4+ предоставляет возможность лениво загружать данные через loader. Можно комбинировать ленивые компоненты с loader, чтобы минимизировать задержки:

const Products = lazy(() => import("./pages/Products"));

const router = createBrowserRouter([
  {
    path: "/products",
    element: <Products />,
    loader: async () => {
      const response = await fetch("/api/products");
      return response.json();
    },
  },
]);

Таким образом, компонент и данные для него загружаются только при переходе на маршрут.


Оптимизация и рекомендации

  1. Деление по страницам: Ленивые компоненты лучше всего использовать для крупных страниц, а не для маленьких UI-компонентов, иначе нагрузка на асинхронный рендер превысит выигрыш.
  2. Fallback UI: Для улучшения UX необходимо продумать отображение индикаторов загрузки.
  3. Совмещение с кэшированием: Использование ленивых компонентов совместно с кэшированием данных и компонентных состояний минимизирует повторные загрузки.
  4. Группировка чанков: Для связанных маршрутов можно объединять их в один чанк, чтобы избежать многократных сетевых запросов.

Пример комплексного ленивого роутинга

import { lazy, Suspense } from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";

const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));
const Dashboard = lazy(() => import("./pages/Dashboard"));

const router = createBrowserRouter([
  { path: "/", element: <Home /> },
  { path: "/about", element: <About /> },
  {
    path: "/dashboard",
    element: <Dashboard />,
    children: [
      { path: "settings", element: lazy(() => import("./pages/Settings")) },
    ],
  },
]);

<Suspense fallback={<div>Загрузка приложения...</div>}>
  <RouterProvider router={router} />
</Suspense>

В этом примере реализована полная ленивость: каждая страница загружается отдельно, а вложенные маршруты тоже обрабатываются асинхронно, что обеспечивает высокую производительность и гибкость.