useMemo и useCallback
Знакомимся с хуками оптимизации: useMemo кеширует значение, useCallback — функцию. И учимся не злоупотреблять ими.
useMemo запоминает результат вычисления между рендерами; useCallback запоминает саму функцию. Оба пересчитывают значение только при смене зависимостей.
Проблема: лишние вычисления
При каждой перерисовке тело компонента выполняется заново. Если внутри есть тяжёлое вычисление, оно повторяется даже когда исходные данные не менялись:
function ProductList({ products, query }) {
// выполняется при КАЖДОЙ перерисовке, даже если products не менялся
const filtered = products.filter((p) => p.name.includes(query));
return <List items={filtered} />;
}
useMemo: кеш результата
useMemo сохраняет результат и пересчитывает его только при изменении зависимостей:
import { useMemo } from "react";
function ProductList({ products, query }) {
const filtered = useMemo(
() => products.filter((p) => p.name.includes(query)),
[products, query] // пересчёт только при их смене
);
return <List items={filtered} />;
}
Пока products и query те же — React вернёт запомненный массив, не вычисляя заново.
useCallback: кеш функции
При каждой перерисовке функции, объявленные в компоненте, создаются заново — это новые объекты. Обычно это неважно, но если функция передаётся в оптимизированный дочерний компонент, новая ссылка заставит его перерисоваться зря. useCallback сохраняет ту же функцию между рендерами:
import { useCallback } from "react";
function Parent({ items }) {
const handleSelect = useCallback((id) => {
console.log("Выбран", id);
}, []); // та же функция между рендерами
return <ChildList items={items} onSelect={handleSelect} />;
}
По сути useCallback(fn, deps) — это сокращение для useMemo(() => fn, deps).
Стабильность ссылок на чистом JS
Чтобы понять, почему «новая функция = новая ссылка» вообще проблема, посмотрите, как сравниваются функции и объекты в JS:
function makeFn() {
return () => "привет";
}
const a = makeFn();
const b = makeFn();
console.log(a === b); // разные объекты-функции → false
const same = a;
console.log(a === same); // та же ссылка → true
Вывод:
false true
Каждый «новый» объект или функция — это новая ссылка (false). Оптимизированные компоненты сравнивают пропсы по ссылке, поэтому новая функция выглядит для них как «изменившийся проп». useCallback возвращает ту же ссылку — и лишней перерисовки не происходит.
Не оптимизируйте вслепую
Главная ошибка — оборачивать в useMemo/useCallback всё подряд. Это само по себе стоит памяти и усложняет код. Применяйте их только когда:
- вычисление действительно тяжёлое, или
- функция/значение передаётся в оптимизированный (мемоизированный) дочерний компонент.
В большинстве случаев React и так достаточно быстр — преждевременная оптимизация лишь вредит читаемости.
Итог
useMemoкеширует результат вычисления,useCallback— саму функцию; оба обновляются по зависимостям.- Польза — избежать тяжёлых пересчётов и лишних перерисовок мемоизированных детей из-за новых ссылок.
- Не оборачивайте в них всё подряд: применяйте точечно, иначе только усложните код.