← Назад к вопросам
Что такое мемоизация и когда её следует применять?
2.0 Middle🔥 231 комментариев
#JavaScript Core#React#Оптимизация и производительность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Мемоизация и её применение
Мемоизация — это техника оптимизации производительности, при которой функция сохраняет результаты её вызовов с определёнными аргументами и возвращает кэшированный результат при повторном вызове с теми же аргументами. Это уменьшает количество ненужных вычислений.
Основная идея
// Без мемоизации
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// fibonacci(5) вычисляет:
// fibonacci(4) и fibonacci(3)
// fibonacci(4) = fibonacci(3) + fibonacci(2) (fibonacci(3) вычисляется СНОВА!)
// Много повторных вычислений
console.time('without memo');
fibonacci(40); // Займёт несколько секунд!
console.timeEnd('without memo');
// С мемоизацией
const memo = {};
function fibonacciMemo(n) {
if (n in memo) return memo[n]; // Возвращаю кэш
if (n <= 1) return n;
const result = fibonacciMemo(n - 1) + fibonacciMemo(n - 2);
memo[n] = result; // Сохраняю результат
return result;
}
console.time('with memo');
fibonacciMemo(40); // Мгновенно!
console.timeEnd('with memo');
// Результат: fibonacci(40) вычисляется один раз для каждого n
Создание функции-обёртки для мемоизации
// Универсальная функция мемоизации
function memoize(fn) {
const cache = {};
return function(...args) {
// Создаю ключ кэша из аргументов
const key = JSON.stringify(args);
// Если результат уже есть в кэше
if (key in cache) {
console.log(`Возвращаю из кэша для ${key}`);
return cache[key];
}
// Иначе вычисляю результат
const result = fn.apply(this, args);
cache[key] = result;
return result;
};
}
// Использование
const add = (a, b) => {
console.log(`Вычисляю ${a} + ${b}`);
return a + b;
};
const memoizedAdd = memoize(add);
memoizedAdd(5, 3); // Вычисляю 5 + 3 → 8
memoizedAdd(5, 3); // Возвращаю из кэша → 8 (без вывода консоли)
memoizedAdd(2, 4); // Вычисляю 2 + 4 → 6
Мемоизация в React: useMemo
import { useMemo } from 'react';
function MyComponent({ items }) {
// Без мемоизации: вычисляется при каждом рендере
const expensiveCalculation = items.reduce((sum, item) => {
console.log('Вычисляю sum...');
return sum + item.value;
}, 0);
return <div>Сумма: {expensiveCalculation}</div>;
}
// С мемоизацией: вычисляется только когда items изменяется
function MyComponentOptimized({ items }) {
const expensiveCalculation = useMemo(() => {
console.log('Вычисляю sum...');
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]); // зависимость: items
return <div>Сумма: {expensiveCalculation}</div>;
}
Мемоизация React компонентов: React.memo
// Компонент, который перерендеривается даже если props не изменились
function UserCard({ user }) {
console.log('Рендерю UserCard для', user.name);
return <div>{user.name} - {user.email}</div>;
}
// Мемоизированный компонент
const MemoizedUserCard = React.memo(UserCard);
function App() {
const [count, setCount] = React.useState(0);
const user = { name: 'Alice', email: 'alice@example.com' };
return (
<div>
<button onClick={() => setCount(count + 1)}>Счётчик: {count}</button>
<MemoizedUserCard user={user} />
{/* UserCard рендерится только когда меняется user */}
</div>
);
}
Мемоизация обработчиков: useCallback
function Parent() {
const [count, setCount] = React.useState(0);
// ❌ Без мемоизации: новая функция при каждом рендере
const handleClick = () => {
console.log('Нажата кнопка');
};
// ✅ С мемоизацией: одна функция, пока зависимости не изменятся
const memoizedHandleClick = React.useCallback(() => {
console.log('Нажата кнопка');
}, []); // нет зависимостей
return (
<div>
<button onClick={() => setCount(count + 1)}>Счётчик: {count}</button>
<Child onClick={memoizedHandleClick} />
</div>
);
}
const Child = React.memo(({ onClick }) => {
console.log('Рендерю Child');
return <button onClick={onClick}>Дочерняя кнопка</button>;
});
Когда применять мемоизацию
1. Дорогостоящие вычисления
// Расчёт статистики по большому массиву
const calculateStats = useMemo(() => {
return data.map(item => ({
...item,
percentile: calculatePercentile(item.value, data),
}));
}, [data]);
2. Функции, передаваемые как props
// ❌ Плохо: новая функция при каждом рендере
function Parent() {
return <Expensive onClick={() => console.log('clicked')} />;
}
// ✅ Хорошо: стабильная функция
function Parent() {
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return <Expensive onClick={handleClick} />;
}
3. Частые перерендеры, редкие изменения данных
// Компонент перерендеривается часто, но props меняются редко
const ExpensiveComponent = React.memo(function({ largeList }) {
const processedList = useMemo(() => {
return largeList.filter(item => item.active).sort();
}, [largeList]);
return (
<ul>
{processedList.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
});
4. Длинные цепочки зависимостей
function Dashboard({ userId, filters, timeRange }) {
// Мемоизирую объект, чтобы не создавалась новая ссылка
const queryParams = useMemo(() => ({
userId,
...filters,
startDate: timeRange.start,
endDate: timeRange.end,
}), [userId, filters, timeRange]);
// Используется в useEffect
useEffect(() => {
fetchData(queryParams);
}, [queryParams]);
}
Когда НЕ использовать мемоизацию
1. Простые вычисления
// ❌ Излишняя мемоизация
const sum = useMemo(() => a + b, [a, b]);
// ✅ Просто вычисли
const sum = a + b;
2. Примитивные значения в props
// ❌ Не нужна React.memo
const Button = React.memo(({ label, count }) => {
return <button>{label} {count}</button>;
});
// ✅ Просто компонент
function Button({ label, count }) {
return <button>{label} {count}</button>;
}
3. Неправильные зависимости
// ❌ Плохо: зависимость неправильная
const result = useMemo(() => {
return data.filter(item => item.userId === userId);
}, [data]); // забыли userId!
// ✅ Правильно
const result = useMemo(() => {
return data.filter(item => item.userId === userId);
}, [data, userId]);
Практический пример: фильтрация большого списка
function UserList({ users, searchTerm }) {
// Мемоизирую фильтрованный список
const filteredUsers = useMemo(() => {
console.log('Фильтрую пользователей...');
return users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [users, searchTerm]);
return (
<div>
{filteredUsers.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}
При каждом введении символа в поле поиска:
- Без мемоизации: фильтрация выполняется и таблица перерендеривается
- С мемоизацией: фильтрация выполняется только если searchTerm изменился
Правила мемоизации
- Не переусложняй: мемоизация имеет свою стоимость (память, время на сравнение)
- Измеряй производительность: используй DevTools Profiler
- Задай правильные зависимости: иначе мемоизация не будет работать
- Избегай создания новых объектов в зависимостях:
// ❌ Плохо: { name: 'Alice' } создаётся заново при каждом рендере
const result = useMemo(() => {
return processUser({ name: 'Alice' });
}, [{ name: 'Alice' }]); // новый объект = пересчёт
// ✅ Хорошо
const user = useMemo(() => ({ name: 'Alice' }), []);
const result = useMemo(() => {
return processUser(user);
}, [user]);
Итоги
- Мемоизация — кэширование результатов функций
- Применяй для дорогостоящих вычислений, частых перерендеров
- В React: useMemo для значений, useCallback для функций, React.memo для компонентов
- Не переусложняй: профилируй перед оптимизацией
- Правильные зависимости — ключ к успеху