← Назад к вопросам

Как кэшировать данные на Frontend?

1.0 Junior🔥 141 комментариев
#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Кэширование данных на Frontend

Кэширование — это сохранение часто используемых данных в памяти, чтобы не запрашивать их с сервера каждый раз. На фронтенде есть несколько способов кэширования для оптимизации производительности.

1. localStorage / sessionStorage

Это встроенный API браузера для постоянного и сессионного хранилища:

// Сохранение данных (localStorage сохраняет даже после закрытия браузера)
localStorage.setItem('user', JSON.stringify({ name: 'Alice', id: 1 }));

// Получение данных
const user = JSON.parse(localStorage.getItem('user'));

// Удаление
localStorage.removeItem('user');
localStorage.clear(); // Очистить всё

// sessionStorage (очищается при закрытии вкладки)
sessionStorage.setItem('temp', 'temporary data');

Проблемы:

  • Размер ограничен (5-10 МБ)
  • Синхронный доступ может блокировать UI
  • Нет автоматического истечения (TTL)
  • Нет типизации в JavaScript

2. React Context + useReducer для состояния приложения

Для глобального состояния, которое не нужно сохранять на диск:

import { createContext, useState, useContext, ReactNode } from 'react';

interface CacheEntry<T> {
  data: T;
  timestamp: number;
  ttl?: number; // Time to live в миллисекундах
}

interface CacheContextType {
  cache: Map<string, CacheEntry<any>>;
  set: <T,>(key: string, data: T, ttl?: number) => void;
  get: <T,>(key: string) => T | null;
  clear: () => void;
}

const CacheContext = createContext<CacheContextType | null>(null);

export function CacheProvider({ children }: { children: ReactNode }) {
  const [cache] = useState<Map<string, CacheEntry<any>>>(new Map());

  const set = <T,>(key: string, data: T, ttl?: number) => {
    cache.set(key, {
      data,
      timestamp: Date.now(),
      ttl
    });
  };

  const get = <T,>(key: string): T | null => {
    const entry = cache.get(key);
    if (!entry) return null;

    // Проверяем TTL
    if (entry.ttl && Date.now() - entry.timestamp > entry.ttl) {
      cache.delete(key);
      return null;
    }

    return entry.data as T;
  };

  const clear = () => cache.clear();

  return (
    <CacheContext.Provider value={{ cache, set, get, clear }}>
      {children}
    </CacheContext.Provider>
  );
}

export function useCache() {
  const context = useContext(CacheContext);
  if (!context) throw new Error('useCache must be used within CacheProvider');
  return context;
}

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

function UserProfile({ userId }: { userId: string }) {
  const { get, set } = useCache();
  const [user, setUser] = useState(null);

  useEffect(() => {
    // Проверяем кэш
    const cached = get<any>(`user-${userId}`);
    if (cached) {
      setUser(cached);
      return;
    }

    // Запрашиваем с сервера
    fetchUser(userId).then(data => {
      setUser(data);
      // Кэшируем на 5 минут (300000 мс)
      set(`user-${userId}`, data, 300000);
    });
  }, [userId, get, set]);

  return <div>{user?.name}</div>;
}

3. IndexedDB для больших объёмов данных

Для сложного кэширования с индексами:

function openDB() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('appDB', 1);
    
    request.onupgradeneeded = (e) => {
      const db = e.target.result;
      if (!db.objectStoreNames.contains('cache')) {
        const store = db.createObjectStore('cache', { keyPath: 'key' });
        store.createIndex('timestamp', 'timestamp', { unique: false });
      }
    };

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

class IndexedDBCache {
  constructor(private db: IDBDatabase) {}

  async set(key: string, data: any, ttl?: number) {
    const store = this.db.transaction('cache', 'readwrite').objectStore('cache');
    store.put({
      key,
      data,
      timestamp: Date.now(),
      ttl
    });
  }

  async get(key: string) {
    return new Promise((resolve) => {
      const store = this.db.transaction('cache').objectStore('cache');
      const request = store.get(key);
      
      request.onsuccess = () => {
        const entry = request.result;
        if (!entry) {
          resolve(null);
          return;
        }

        if (entry.ttl && Date.now() - entry.timestamp > entry.ttl) {
          this.delete(key);
          resolve(null);
        } else {
          resolve(entry.data);
        }
      };
    });
  }

  async delete(key: string) {
    const store = this.db.transaction('cache', 'readwrite').objectStore('cache');
    store.delete(key);
  }
}

4. HTTP Кэширование (браузерное)

Лучший способ кэширования — правильные HTTP headers:

// Сервер отправляет эти headers
response.headers['Cache-Control'] = 'max-age=3600'; // 1 час
response.headers['ETag'] = '"hash-of-content"';
response.headers['Last-Modified'] = 'Wed, 01 Jan 2024 00:00:00 GMT';

// Браузер автоматически кэширует и переиспользует
fetch('/api/data')
  // Второй раз: из браузерного кэша (304 Not Modified)
  // Без переноса по сети!

5. React Query для кэширования запросов (рекомендуется)

Лучшая практика для production приложений:

import { useQuery } from '@tanstack/react-query';

function UserProfile({ userId }: { userId: string }) {
  const { data: user, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
    staleTime: 5 * 60 * 1000, // 5 минут "свежо"
    gcTime: 10 * 60 * 1000, // 10 минут в памяти (раньше cacheTime)
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error</div>;

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

Преимущества React Query:

  • Автоматическое кэширование
  • Smart refetching (обновление устаревших данных)
  • Фоновое обновление
  • Управление ошибками
  • DevTools для отладки

6. SWR (Stale-While-Revalidate)

Легче, чем React Query:

import useSWR from 'swr';

function UserProfile({ userId }: { userId: string }) {
  const { data: user, error, isLoading } = useSWR(
    `/api/users/${userId}`,
    (url) => fetch(url).then(r => r.json()),
    {
      revalidateOnFocus: false,
      dedupingInterval: 60000, // 1 минута
    }
  );

  return <div>{user?.name}</div>;
}

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

Cache-First (Offline-first):

async function getCachedData(key, fetcher, ttl = 300000) {
  // 1. Проверяем кэш
  const cached = localStorage.getItem(key);
  if (cached) {
    const { data, timestamp } = JSON.parse(cached);
    if (Date.now() - timestamp < ttl) {
      return data;
    }
  }

  // 2. Если нет или устарело, запрашиваем с сервера
  const data = await fetcher();
  localStorage.setItem(key, JSON.stringify({
    data,
    timestamp: Date.now()
  }));
  return data;
}

Network-First:

async function getNetworkFirst(key, fetcher, ttl = 300000) {
  try {
    // 1. Пытаемся получить с сервера
    const data = await fetcher();
    localStorage.setItem(key, JSON.stringify({
      data,
      timestamp: Date.now()
    }));
    return data;
  } catch (error) {
    // 2. Если ошибка, берём из кэша
    const cached = localStorage.getItem(key);
    if (cached) {
      return JSON.parse(cached).data;
    }
    throw error;
  }
}

8. Практический пример: кэш с TTL

class AppCache {
  private cache = new Map<string, { data: any; expires: number }>();

  set(key: string, data: any, ttl: number = 5 * 60 * 1000) {
    this.cache.set(key, {
      data,
      expires: Date.now() + ttl
    });
  }

  get(key: string) {
    const entry = this.cache.get(key);
    if (!entry) return null;

    if (Date.now() > entry.expires) {
      this.cache.delete(key);
      return null;
    }

    return entry.data;
  }

  async fetchWithCache(key: string, fetcher: () => Promise<any>) {
    const cached = this.get(key);
    if (cached) return cached;

    const data = await fetcher();
    this.set(key, data);
    return data;
  }
}

// Использование
const cache = new AppCache();
const users = await cache.fetchWithCache('users', () =>
  fetch('/api/users').then(r => r.json())
);

Сравнение методов

МетодРазмерТипКогда использовать
localStorage5-10 МБСинхронныйПростые данные, настройки
sessionStorage5-10 МБСинхронныйВременные данные сессии
Memory (Map)RAMСинхронныйБыстрый кэш, пока открыта страница
IndexedDB50+ МБАсинхронныйБольшие объёмы данных
React QueryRAMАсинхронныйAPI запросы (ЛУЧШИЙ ВЫБОР)
SWRRAMАсинхронныйПростые запросы

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

  1. Используй React Query или SWR для API запросов
  2. Установи TTL для каждого кэша
  3. Кэшируй только стабильные данные (профиль пользователя, каталог товаров)
  4. НЕ кэшируй личные данные, пароли, токены
  5. Предусмотри инвалидацию кэша при обновлении данных
  6. Используй HTTP кэширование для статических ресурсов

Резюме

Кэширование на фронтенде:

  • localStorage — простые данные
  • React Context — глобальное состояние
  • React Query — API запросы (рекомендуется)
  • IndexedDB — большие объёмы
  • HTTP headers — браузерное кэширование

Для production выбирай React Query — это стандарт индустрии.

Как кэшировать данные на Frontend? | PrepBro