Кеширование результатов

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


Кеширование на уровне маршрутов

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

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

async function userLoader({ params }) {
  const response = await fetch(`/api/users/${params.userId}`);
  return response.json();
}

const router = createBrowserRouter([
  {
    path: "/users/:userId",
    element: <UserPage />,
    loader: userLoader
  }
]);

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

Механизм кеширования loader

Loader функции можно комбинировать с клиентским кешированием для предотвращения повторных запросов. Одним из простых подходов является использование Map или WeakMap для хранения результатов:

const cache = new Map();

async function cachedUserLoader({ params }) {
  const key = params.userId;
  if (cache.has(key)) {
    return cache.get(key);
  }
  const response = await fetch(`/api/users/${key}`);
  const data = await response.json();
  cache.set(key, data);
  return data;
}

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


Использование defer и ленивой загрузки данных

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

import { defer, Await } from "react-router-dom";

async function postsLoader() {
  const response = await fetch("/api/posts");
  return defer({ posts: response.json() });
}

function Posts() {
  const data = useLoaderData();

  return (
    <Suspense fallback={<div>Загрузка...</div>}>
      <Await resolve={data.posts}>
        {(posts) => (
          <ul>
            {posts.map(post => <li key={post.id}>{post.title}</li>)}
          </ul>
        )}
      </Await>
    </Suspense>
  );
}

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

  • defer позволяет рендерить компонент до того, как все данные загружены.
  • Асинхронные данные кешируются на уровне маршрута до тех пор, пока компонент остаётся активным.

Стратегии кеширования

1. Локальный кеш на клиенте

  • Используется Map, WeakMap или стейт React (useState, useReducer).
  • Подходит для небольших приложений с ограниченным количеством маршрутов.

2. Сторонние библиотеки

  • React Query / TanStack Query идеально интегрируется с React Router.
  • Автоматически управляет кешем, повторными запросами и актуализацией данных.
  • Пример интеграции:
import { useQuery } from "@tanstack/react-query";

function User({ userId }) {
  const { data } = useQuery(["user", userId], () =>
    fetch(`/api/users/${userId}`).then(res => res.json())
  );

  return <div>{data.name}</div>;
}

3. Кеширование на уровне HTTP

  • Использование заголовков Cache-Control на сервере.
  • React Router не вмешивается напрямую, но можно комбинировать с loader для повторного использования уже закешированных данных браузера.

Управление временем жизни кеша

При работе с кешем важно учитывать устаревание данных:

  • Для локального кеша можно использовать структуру вида { data, timestamp }.
  • При каждом запросе проверять возраст данных и обновлять их при необходимости.
const cache = new Map();
const TTL = 5 * 60 * 1000; // 5 минут

async function timedUserLoader({ params }) {
  const key = params.userId;
  const cached = cache.get(key);

  if (cached && Date.now() - cached.timestamp < TTL) {
    return cached.data;
  }

  const response = await fetch(`/api/users/${key}`);
  const data = await response.json();
  cache.set(key, { data, timestamp: Date.now() });
  return data;
}

Кеширование навигации и переходов

React Router предоставляет хук useNavigation, который позволяет отслеживать переходы и оптимизировать кеширование данных между страницами:

  • Можно предзагружать данные для маршрута до его активации.
  • Обеспечивает плавный UX при переключении между страницами с одинаковыми или схожими данными.
import { useNavigation, useLoaderData } from "react-router-dom";

function Dashboard() {
  const navigation = useNavigation();
  const data = useLoaderData();

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

  return <div>{data.summary}</div>;
}

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

  • Минимизировать повторные запросы: кешировать результаты loader и повторно использовать их.
  • Использовать TTL или стратегию инвалидации: предотвращает устаревание данных.
  • Комбинировать ленивую загрузку и кеширование: defer с локальным или внешним кешем ускоряет отображение.
  • Следить за памятью: особенно при использовании Map для больших объемов данных.

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