React.memo, useMemo и useCallback: когда и что использовать для оптимизации
Оптимизация рендеринга в React — тема, которая волнует почти каждого разработчика, особенно когда проект начинает обрастать сложной логикой, а производительность падает. В этой статье разберёмся, как грамотно использовать React.memo, useMemo и useCallback, чтобы не просто «запихнуть мемоизацию куда-то на всякий случай», а действительно улучшить производительность. Разберём реальные кейсы, нюансы и неожиданные решения, которые помогут вам писать не просто работающий, а быстрый и эффективный React-код.
React.memo: не просто обёртка, а инструмент
Как работает и где помогает

Если вы хоть раз сталкивались с нежелательными повторными рендерами дочерних компонентов, скорее всего, вы уже слышали про React.memo. Это HOC (Higher-Order Component), который позволяет избежать лишнего рендера, если пропсы не изменились. Но вот что важно: сравнение пропсов по умолчанию поверхностное. Это значит, что если вы передаёте объект или функцию, React под капотом сравнит ссылки, а не значения. Поэтому React.memo без дополнительных ухищрений может оказаться бесполезным.
const MyComponent = React.memo(({ title }) => {
console.log('Ререндер:', title);
return <div>{title}</div>;
});
В реальной практике React.memo примеры чаще всего встречаются в UI-библиотеках, где компоненты часто оборачивают для предотвращения лишнего рендера при изменении глобального состояния. Например, в таблицах с десятками строк или карточках товаров на маркетплейсе.
Нестандартное решение
Если вам нужно передавать сложные объекты в React.memo компонент, используйте кастомную функцию сравнения пропсов. Это особенно актуально, когда компонент получает конфигурацию, например, фильтры с множеством параметров.
const areEqual = (prevProps, nextProps) => {
return JSON.stringify(prevProps.filters) === JSON.stringify(nextProps.filters);
};
export default React.memo(FilterPanel, areEqual);
Да, JSON.stringify может быть дорогим удовольствием, но если объект небольшой и ререндер действительно критичен — это может быть оправдано. В некоторых проектах это решение дало прирост FPS на 15–20% при скролле списка с фильтрами.
useMemo: когда кеширование действительно нужно
Что делает и когда стоит использовать
Хук useMemo позволяет мемоизировать результат вычислений, чтобы не пересчитывать его при каждом ререндере. Но вот главная ошибка: многие используют его по инерции, даже там, где он не нужен. А это, между прочим, тоже затраты — на хранение в памяти и вызов самой функции-хука.
Так когда использовать useMemo? Прежде всего — при дорогих операциях: фильтрации больших массивов, вычислениях на основе сложных данных или генерации конфигураций для компонентов (например, колонок таблицы).
const filteredList = useMemo(() => {
return bigList.filter(item => item.active);
}, [bigList]);
В одном проекте у нас был компонент, где таблица с 5000 строк фильтровалась по множеству параметров. Без useMemo каждый ввод в поле фильтра вызывал лаг в 200–300 мс. С мемоизацией отклик стал практически мгновенным.
Нестандартное применение
Иногда можно использовать useMemo для создания стабильных ссылок на объекты-конфигурации, которые передаются в мемоизированные компоненты. Это особенно полезно, когда вы используете React.memo vs useMemo в связке, чтобы обеспечить стабильность пропсов.
const columns = useMemo(() => [
{ header: 'Имя', accessor: 'name' },
{ header: 'Возраст', accessor: 'age' }
], []);
Это гарантирует, что компонент таблицы не будет перерендериваться из-за новых ссылок на пропсы при каждом ререндере родителя.
useCallback: спасение от анонимных функций
Когда и зачем использовать useCallback
Если вы передаёте функции как пропсы в дочерние компоненты, особенно мемоизированные, вы уже сталкивались с тем, что эти функции пересоздаются каждый раз. Именно тут и приходит на помощь useCallback. Он возвращает мемоизированную версию колбэка, которая сохраняет ссылку, пока зависимости не изменятся.
Так зачем нужен useCallback на практике? Например, у вас есть список карточек, каждая из которых принимает onClick. Если вы не мемоизируете эту функцию, каждая карточка будет считать, что пропсы изменились, и перерендерится.
const handleClick = useCallback((id) => {
console.log('Click on', id);
}, []);
В одной из наших админок оптимизация с useCallback дала прирост производительности при навигации по списку на 30% — просто потому, что перестали перерендериваться десятки карточек при каждом обновлении родителя.
Необычный подход
Иногда useCallback можно использовать как способ стабилизировать функции в зависимостях useEffect. Это особенно полезно, если эффект должен сработать только при реальном изменении логики, а не из-за новой ссылки на колбэк.
const fetchData = useCallback(() => {
// fetch logic
}, [filters]);
useEffect(() => {
fetchData();
}, [fetchData]);
Такой приём предотвращает лишние вызовы эффекта и делает поведение компонента более предсказуемым.
Итог: думай прежде чем мемоизировать

Оптимизация React-приложения — это не про то, чтобы везде воткнуть React.memo и надеяться на лучшее. Это про понимание, где действительно происходят лишние рендеры и какие данные стоит стабилизировать. Используйте React.memo для компонентов, которые часто получают одни и те же пропсы. Применяйте useMemo там, где вычисления действительно дорогие. И не забывайте про useCallback, если передаёте функции в мемоизированные компоненты.
Но главное — не переусердствуйте. Любая мемоизация — это компромисс между производительностью и читаемостью кода. И если она не даёт ощутимого прироста, возможно, она вам просто не нужна.
Если вы хотите увидеть реальные React.memo примеры или разобраться, зачем нужен useCallback в контексте вашего проекта — не бойтесь проводить замеры, профилировать и экспериментировать. Только так вы найдёте баланс между быстродействием и удобством разработки.



