React.memo, usememo и usecallback для оптимизации производительности компонентов

React.memo, useMemo и useCallback: когда и что использовать для оптимизации

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

React.memo: не просто обёртка, а инструмент

Как работает и где помогает

React.memo, useMemo и useCallback: когда и что использовать для оптимизации - иллюстрация

Если вы хоть раз сталкивались с нежелательными повторными рендерами дочерних компонентов, скорее всего, вы уже слышали про 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.memo, useMemo и useCallback: когда и что использовать для оптимизации - иллюстрация

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

Но главное — не переусердствуйте. Любая мемоизация — это компромисс между производительностью и читаемостью кода. И если она не даёт ощутимого прироста, возможно, она вам просто не нужна.

Если вы хотите увидеть реальные React.memo примеры или разобраться, зачем нужен useCallback в контексте вашего проекта — не бойтесь проводить замеры, профилировать и экспериментировать. Только так вы найдёте баланс между быстродействием и удобством разработки.

Прокрутить вверх