Индикаторы загрузки

При разработке приложений на React с использованием React Router часто возникает необходимость отображать индикаторы загрузки при асинхронной подгрузке компонентов или данных. Это особенно важно для маршрутов, где контент загружается динамически с сервера. React Router предоставляет несколько инструментов и паттернов для решения этой задачи.


Использование Suspense и ленивой загрузки компонентов

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

import { Suspense, lazy } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";

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

function App() {
  return (
    <Router>
      <Suspense fallback={<div>Загрузка...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

Ключевые моменты:

  • fallback внутри Suspense определяет компонент, отображаемый во время загрузки.
  • Можно использовать любой JSX-код, включая анимации или спиннеры.
  • Ленивые компоненты позволяют уменьшить начальный размер бандла и ускорить первую загрузку.

Загрузка данных с использованием loader в React Router v6.4+

Начиная с версии 6.4, React Router поддерживает концепцию loaders — функций для асинхронного получения данных перед рендером маршрута. Это позволяет интегрировать индикаторы загрузки непосредственно в маршрутизацию.

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

async function homeLoader() {
  const response = await fetch("/api/home");
  return response.json();
}

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

function App() {
  return <RouterProvider router={router} />;
}

Чтобы показывать индикатор загрузки во время выполнения loader, применяют компонент useNavigation:

import { useNavigation } from "react-router-dom";

function AppLayout({ children }) {
  const navigation = useNavigation();

  return (
    <div>
      {navigation.state === "loading" && <div>Загрузка данных...</div>}
      {children}
    </div>
  );
}

Особенности:

  • navigation.state может принимать значения idle, loading, submitting.
  • Компонент отображается автоматически при смене состояния маршрута.
  • Подходит для глобальных индикаторов или прогресс-баров.

Локальные индикаторы загрузки внутри компонентов

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

import { useState, useEffect } from "react";

function Profile() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("/api/profile")
      .then(res => res.json())
      .then(user => {
        setData(user);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>Загрузка профиля...</div>;
  return <div>Имя пользователя: {data.name}</div>;
}

Особенности подхода:

  • Полный контроль над состоянием загрузки внутри компонента.
  • Возможность добавлять таймауты, обработку ошибок и анимации.
  • Комбинируется с глобальными индикаторами и маршрутизатором.

Прогресс-бары и визуальные индикаторы

Для улучшения UX можно использовать линейные прогресс-бары, которые показывают постепенную загрузку данных или переход между страницами. Например, библиотека nprogress легко интегрируется с React Router:

import NProgress from "nprogress";
import { useNavigation } from "react-router-dom";
import { useEffect } from "react";

function NavigationProgress() {
  const navigation = useNavigation();

  useEffect(() => {
    if (navigation.state === "loading") NProgress.start();
    else NProgress.done();
  }, [navigation.state]);

  return null;
}

Вставка компонента NavigationProgress в корневой layout позволяет отслеживать все переходы и асинхронные загрузки, обеспечивая плавный пользовательский опыт.


Рекомендации по организации индикаторов

  • Разделять глобальные и локальные индикаторы. Глобальные подходят для переходов между страницами, локальные — для загрузки данных внутри компонента.
  • Использовать ленивую загрузку компонентов для крупных страниц или модулей.
  • Синхронизировать индикаторы с состоянием loader и navigation.state в React Router v6.4+ для точной реакции на асинхронные действия.
  • Добавлять обработку ошибок, чтобы при сбое загрузки не оставался бесконечно работающий индикатор.

Эти подходы позволяют создать плавное и отзывчивое приложение, где пользователь всегда видит актуальный статус загрузки контента.