← Назад к вопросам
Для чего кешировать функцию?
2.2 Middle🔥 221 комментариев
#JavaScript Core#Оптимизация и производительность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Кеширование функции — оптимизация производительности
Кеширование функции (memoization) — это техника оптимизации, которая сохраняет результаты дорогостоящих вычислений для одних и тех же входных данных. Вместо повторного вычисления функция возвращает сохранённый результат.
Зачем нужно кеширование
// ❌ Без кеширования — медленно
function fibonacci(n: number): number {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.time("fib");
console.log(fibonacci(40)); // Очень долго считает
console.timeEnd("fib"); // ~10 секунд
// ✅ С кешированием — быстро
function memoFibonacci(n: number, cache: Record<number, number> = {}): number {
if (n in cache) {
console.log(`Возвращаем закешированное значение ${n}`);
return cache[n];
}
if (n <= 1) return n;
cache[n] = memoFibonacci(n - 1, cache) + memoFibonacci(n - 2, cache);
return cache[n];
}
console.time("memoFib");
console.log(memoFibonacci(40)); // Почти мгновенно
console.timeEnd("memoFib"); // ~1 миллисекунда
Основные причины кеширования
- Производительность — избегаем дорогостоящих вычислений
- Сокращение нагрузки на сервер — кешируем результаты API запросов
- Улучшение UX — быстрый отклик приложения
- Экономия батареи и памяти — меньше обработок
- Работа offline — используем закешированные данные
Кеширование в React: useMemo
import { useMemo } from "react";
interface Product {
id: number;
name: string;
price: number;
quantity: number;
}
function ShoppingCart({ products }: { products: Product[] }) {
// ❌ Без useMemo — пересчитывается при каждом рендере
const total = products.reduce((sum, p) => sum + (p.price * p.quantity), 0);
// ✅ С useMemo — пересчитывается только если products изменился
const memoTotal = useMemo(() => {
console.log("Пересчитываем total");
return products.reduce((sum, p) => sum + (p.price * p.quantity), 0);
}, [products]);
const taxAmount = useMemo(() => {
console.log("Пересчитываем налог");
return memoTotal * 0.1;
}, [memoTotal]);
return (
<div>
<p>Сумма товаров: \${memoTotal}</p>
<p>Налог: \${taxAmount}</p>
<p>Итого: \${memoTotal + taxAmount}</p>
</div>
);
}
// Если products не изменился, useMemo вернёт старое значение
// "Пересчитываем total" не выведется
Кеширование компонентов: React.memo
import { memo } from "react";
interface UserCardProps {
userId: number;
onSelect: (id: number) => void;
}
// ❌ Без memo — перерисовывается при каждом рендере родителя
function UserCardUnmemoized({ userId, onSelect }: UserCardProps) {
console.log(`Рендер UserCard ${userId}`);
return (
<div>
<h3>Пользователь {userId}</h3>
<button onClick={() => onSelect(userId)}>Выбрать</button>
</div>
);
}
// ✅ С memo — перерисовывается только если props изменились
const UserCard = memo(function UserCard({ userId, onSelect }: UserCardProps) {
console.log(`Рендер UserCard ${userId}`);
return (
<div>
<h3>Пользователь {userId}</h3>
<button onClick={() => onSelect(userId)}>Выбрать</button>
</div>
);
});
// В родительском компоненте
function UserList() {
const [selected, setSelected] = useState<number | null>(null);
const users = [1, 2, 3];
// Проблема: обработчик создаётся заново при каждом рендере
const handleSelect = (id: number) => {
setSelected(id);
};
return (
<div>
{users.map(userId => (
<UserCard
key={userId}
userId={userId}
onSelect={handleSelect} // Новая функция каждый раз
/>
))}
</div>
);
}
useCallback — кеширование функции
import { useCallback } from "react";
function UserList() {
const [selected, setSelected] = useState<number | null>(null);
const users = [1, 2, 3];
// ❌ Без useCallback — новая функция при каждом рендере
const handleSelectBad = (id: number) => {
setSelected(id);
};
// ✅ С useCallback — одна и та же функция пока зависимостей нет
const handleSelectGood = useCallback((id: number) => {
setSelected(id);
}, []); // Зависимости пусты, функция никогда не пересоздаётся
return (
<div>
{users.map(userId => (
<UserCard
key={userId}
userId={userId}
onSelect={handleSelectGood}
/>
))}
</div>
);
}
Кеширование API запросов
import { useState, useEffect } from "react";
// ✅ Простой кеш для API запросов
const apiCache = new Map<string, Promise<any>>();
async function cachedFetch(url: string): Promise<any> {
// Если результат уже в кеше, вернуть его
if (apiCache.has(url)) {
console.log(`Возвращаем кешированный результат для ${url}`);
return apiCache.get(url);
}
// Иначе, сделать запрос и кешировать
console.log(`Делаем запрос ${url}`);
const promise = fetch(url)
.then(r => r.json())
.catch(err => {
// Если ошибка, удалить из кеша
apiCache.delete(url);
throw err;
});
apiCache.set(url, promise);
return promise;
}
// Использование в компоненте
function UserProfile({ userId }: { userId: number }) {
const [user, setUser] = useState<any>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
cachedFetch(`/api/users/${userId}`)
.then(setUser)
.finally(() => setLoading(false));
}, [userId]);
if (loading) return <p>Загрузка...</p>;
return <div>{user?.name}</div>;
}
// Кеш будет работать на протяжении всего жизненного цикла приложения
// Но для production нужна более сложная стратегия (TTL, размер и т.п.)
Реализация простого кеширования функции
// Функция для создания кешированной версии
function memoize<T extends (...args: any[]) => any>(fn: T): T {
const cache = new Map();
return ((...args: any[]) => {
// Создаём ключ из аргументов
const key = JSON.stringify(args);
// Если в кеше, вернуть
if (cache.has(key)) {
console.log("Из кеша:", key);
return cache.get(key);
}
// Иначе, вычислить и кешировать
const result = fn(...args);
cache.set(key, result);
console.log("Вычислено:", key);
return result;
}) as T;
}
// Дорогостоящая функция
function expensiveCalculation(a: number, b: number): number {
console.time("Вычисление");
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result += i % (a + b);
}
console.timeEnd("Вычисление");
return result;
}
const memoExpensive = memoize(expensiveCalculation);
memoExpensive(5, 10); // Вычисление ~ 500мс
memoExpensive(5, 10); // Из кеша ~ 0мс (мгновенно)
memoExpensive(5, 11); // Вычисление ~ 500мс (другие аргументы)
Когда НЕ использовать кеширование
// ❌ Бессмысленно кешировать быстрые операции
const memoAdd = useMemo(() => 2 + 2, []); // Зачем? Это быстро
// ❌ Кеширование занимает больше памяти, чем сэкономит
function SearchResults({ term }: { term: string }) {
// Если term меняется часто, кеш не поможет
const results = useMemo(() => {
return expensiveSearch(term);
}, [term]);
}
// ❌ Помните о пропускной способности памяти
const cache = new Map();
for (let i = 0; i < 1000000; i++) {
cache.set(i, new Array(10000).fill(i)); // Утечка памяти!
}
Лучшие практики
- Кешируйте только дорогостоящие вычисления
- Помните о размере кеша — может кончиться память
- Используйте TTL (Time To Live) для инвалидации устаревших данных
- Тестируйте производительность перед и после кеширования
- Не переусложняйте — иногда без кеша быстрее