← Назад к вопросам
Как кэшировать данные на 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())
);
Сравнение методов
| Метод | Размер | Тип | Когда использовать |
|---|---|---|---|
| localStorage | 5-10 МБ | Синхронный | Простые данные, настройки |
| sessionStorage | 5-10 МБ | Синхронный | Временные данные сессии |
| Memory (Map) | RAM | Синхронный | Быстрый кэш, пока открыта страница |
| IndexedDB | 50+ МБ | Асинхронный | Большие объёмы данных |
| React Query | RAM | Асинхронный | API запросы (ЛУЧШИЙ ВЫБОР) |
| SWR | RAM | Асинхронный | Простые запросы |
Лучшие практики
- Используй React Query или SWR для API запросов
- Установи TTL для каждого кэша
- Кэшируй только стабильные данные (профиль пользователя, каталог товаров)
- НЕ кэшируй личные данные, пароли, токены
- Предусмотри инвалидацию кэша при обновлении данных
- Используй HTTP кэширование для статических ресурсов
Резюме
Кэширование на фронтенде:
- localStorage — простые данные
- React Context — глобальное состояние
- React Query — API запросы (рекомендуется)
- IndexedDB — большие объёмы
- HTTP headers — браузерное кэширование
Для production выбирай React Query — это стандарт индустрии.