Стратегии prefetching данных

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

В React Router версии 6 и выше появились встроенные механизмы для управления данными маршрутов через loader-функции. Loader вызывается при переходе на маршрут и может возвращать промис с необходимыми данными. Prefetching заключается в том, чтобы инициировать этот loader заранее.

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import HomePage from "./HomePage";
import AboutPage from "./AboutPage";

const router = createBrowserRouter([
  {
    path: "/",
    element: <HomePage />,
    loader: () => fetch("/api/home").then(res => res.json()),
  },
  {
    path: "/about",
    element: <AboutPage />,
    loader: () => fetch("/api/about").then(res => res.json()),
  },
]);

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

В этом примере loader загружает данные по мере перехода. Чтобы реализовать prefetching, можно использовать useFetcher или события наведения мыши.


Prefetching с помощью useFetcher

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

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

function NavLink({ to, children }) {
  const fetcher = useFetcher();

  return (
    <a
      href={to}
      onMouseEn ter={() => fetcher.load(to)}
    >
      {children}
    </a>
  );
}

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


Prefetching через onFocus и onHover

React Router поддерживает стратегию prefetching, основанную на пользовательских действиях, таких как наведение мыши или фокус на элементе. Использование этих стратегий снижает нагрузку на сеть и ускоряет восприятие интерфейса.

<a
  href="/about"
  onMouseEn ter={() => fetcher.load("/about")}
  onFo cus={() => fetcher.load("/about")}
>
  О нас
</a>

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


Компонент <Link> из React Router 6.4+ поддерживает встроенный prefetch через свойство prefetch. Это позволяет указать стратегию, которая будет автоматически применяться при рендере ссылки.

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

<Link to="/about" prefetch="intent">
  О нас
</Link>

Параметр prefetch="intent" означает, что данные маршрута будут загружены при намерении пользователя перейти (например, при наведении курсора). Альтернативно можно использовать prefetch="render", чтобы загружать данные сразу при рендере компонента.


Prefetching для вложенных маршрутов

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

const router = createBrowserRouter([
  {
    path: "/dashboard",
    element: <Dashboard />,
    loader: () => fetch("/api/dashboard").then(res => res.json()),
    children: [
      {
        path: "stats",
        element: <Stats />,
        loader: () => fetch("/api/stats").then(res => res.json()),
      },
      {
        path: "reports",
        element: <Reports />,
        loader: () => fetch("/api/reports").then(res => res.json()),
      },
    ],
  },
]);

Для prefetching можно вызывать loader конкретного дочернего маршрута, когда пользователь взаимодействует с элементами меню, не загружая сразу всю страницу.


Стратегии оптимизации prefetching

  1. Prefetching по намерению (intent): загружать данные при первом сигнале пользователя о намерении перейти (hover, focus).
  2. Prefetching при рендере (render): загружать данные сразу при монтировании ссылки или навигационного элемента. Подходит для часто посещаемых маршрутов.
  3. Lazy prefetching: загружать данные постепенно, ограничивая количество параллельных запросов для снижения нагрузки на сервер.
  4. Prefetching по приоритету: использовать разные приоритеты для критически важных и второстепенных данных.

Обработка ошибок и кеширование

Loader-функции возвращают промисы, поэтому при prefetching необходимо обрабатывать ошибки. Можно использовать try/catch или встроенные возможности React Router:

loader: async () => {
  try {
    const res = await fetch("/api/data");
    if (!res.ok) throw new Error("Ошибка загрузки");
    return res.json();
  } catch (error) {
    return { error: error.message };
  }
}

Для кеширования prefetching-запросов можно использовать react-query или встроенный кеш fetcher, чтобы данные не загружались повторно при повторном наведении.


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

React Router совместим с React Suspense, что позволяет показывать placeholder-контент, пока данные загружаются:

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

<Suspense fallback={<div>Загрузка...</div>}>
  <Await resolve={loaderData}>
    {(data) => <Component data={data} />}
  </Await>
</Suspense>

Suspense удобно сочетать с prefetching: при переходе пользователь видит индикатор загрузки только в случае, если данные не успели подгрузиться заранее.


Prefetching данных в React Router повышает отзывчивость приложений, сокращает задержки при навигации и позволяет реализовать стратегически продуманные подходы к загрузке ресурсов, от простого hover-prefetch до интеграции с Suspense и кешированием данных.