← Все статьи

Оптимизация производительности React-приложений с useMemo и useCallback

Если вы разрабатываете на React, вы наверняка сталкивались с ситуацией, когда интерфейс начинает «подтормаживать». Компоненты перерисовываются слишком часто, хотя данные не изменились. В консоли инструментов разработчика горит пламя ререндеров. В 2026 году, когда пользователи ожидают идеальной отзывчивости даже на мобильных устройствах, такие проблемы напрямую влияют на конверсию и удержание. Часто корень зла кроется не в сложных алгоритмах, а в неоптимальном использовании базовых возможностей React — хуков useMemo и useCallback.

Многие разработчики воспринимают эти хуки как магические таблетки для производительности и начинают оборачивать в них всё подряд. Это ошибка. Их цель — не ускорение вычислений как таковых, а сохранение стабильных ссылок на значения и функции между рендерами. Ключевое слово здесь — «ссылка». React использует сравнение по ссылке (Object.is), чтобы определить, изменились ли пропсы или зависимости компонента. Если функция или сложный объект создаются заново при каждом рендере, их ссылка меняется, что заставляет зависимые компоненты (например, обернутые в React.memo) перерисовываться без необходимости.

Давайте разберемся на практике. Представьте компонент списка товаров с фильтрацией.

function ProductList({ products, filterText }) { const filteredProducts = products.filter(p => p.name.toLowerCase().includes(filterText.toLowerCase()) ); return <List items={filteredProducts} />; }

Здесь `filteredProducts` будет вычисляться при каждом рендере ProductList, даже если изменился какой-то соседний state, не связанный с `products` или `filterText`. Если фильтрация тяжелая (тысячи товаров), это проблема. Исправим это с помощью useMemo.

function ProductList({ products, filterText }) { const filteredProducts = useMemo(() => { return products.filter(p => p.name.toLowerCase().includes(filterText.toLowerCase()) ); }, [products, filterText]); return <List items={filteredProducts} />; }

Теперь массив `filteredProducts` будет пересчитываться только тогда, когда изменится ссылка на массив `products` или значение `filterText`. Это классический кейс для useMemo: дорогие вычисления.

Теперь рассмотрим useCallback. Он возвращает мемоизированную версию колбэка, которая меняется только при изменении зависимостей. Типичный пример — передача колбэка оптимизированному дочернему компоненту.

function ParentComponent() { const [count, setCount] = useState(0); const handleClick = () => { console.log('Clicked!', count); }; return <HeavyChildComponent onClick={handleClick} />; }

const HeavyChildComponent = React.memo(({ onClick }) => { console.log('HeavyChild render'); return <button onClick={onClick}>Click me</button>; });

При каждом увеличении `count` в ParentComponent создается новая функция `handleClick`. Для HeavyChildComponent это новый пропс `onClick`, поэтому React.memo не сработает и компонент перерисуется. Используем useCallback.

function ParentComponent() { const [count, setCount] = useState(0); const handleClick = useCallback(() => { console.log('Clicked!', count); }, [count]); return <HeavyChildComponent onClick={handleClick} />; }

Теперь ссылка на функцию `handleClick` останется стабильной между рендерами ParentComponent до тех пор, пока не изменится `count`. HeavyChildComponent защищен от лишних ререндеров.

Однако здесь кроется важнейший нюанс для 2026 года: чрезмерное использование этих хуков стало антипаттерном. Они имеют свою стоимость: память для хранения предыдущего значения и время на сравнение зависимостей при каждом рендере. Неправильное применение может даже замедлить приложение.

Давайте выделим четкие критерии, когда использовать эти хуки действительно необходимо:

  • Передача функций или объектов в качестве пропсов компонентам, обернутым в React.memo.
  • Зависимости от этих значений в других хуках (useEffect, useMemo), где важно контролировать срабатывание.
  • Вычисления внутри компонента со значительной вычислительной стоимостью (фильтрация больших массивов, сложные математические операции).

И главные антипаттерны:

  • Обертывание простых примитивных вычислений (сложение двух чисел) в useMemo.
  • Использование useCallback для каждой функции внутри компонента без анализа реального влияния на ререндеры детей.
  • Создание пустых массивов зависимость (`[]`) для функций внутри кастомных хуков или библиотек общего назначения — это может привести к скрытым багам с замыканием устаревших значений переменных состояния.

Современный подход заключается в томчечном анализе с помощью DevTools Profiler и React DevTools (особенно функции подсветки обновлений). Прежде чем добавлять мемоизацию: 1) Измерьте проблему. 2) Найдите конкретный компонент с ненужными ререндерами. 3) Проверьте его пропсы. 4) Только если причиной является новая ссылка на функцию/объект — применяйте соответствующий хук.

В экосистеме React появились и более продвинутые решения для управления производительностью (например,Suspense для данных), но грамотное использование базовых инструментов остается фундаментом быстрого UI.

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

💬 Комментарии (8)
👤
privacy.officer2
21.03.2026 21:48
Нейтрально. Всё это есть в документации React, но для повторения подойдет. Хотя примеры могли бы быть свежее.
👤
marketing.pro2023
24.03.2026 07:10
Актуальная тема! В 2026 действительно нельзя позволять приложениям тормозить. Спасибо за конкретные советы.
👤
nikolay.belov
26.03.2026 22:49
Использовал эти хуки, но иногда забываю про зависимости. Есть ли лайфхаки, чтобы не пропускать их в deps?
👤
sarah.moore34
31.03.2026 04:48
Автор, а как вы относитесь к использованию useMemo для мемоизации JSX? Это вообще хорошая практика или антипаттерн?
👤
barbara.thomas
01.04.2026 18:24
Интересно, а насколько сильно влияет чрезмерное использование useMemo на производительность? Есть ли обратный эффект?
👤
peter.jones_1987
04.04.2026 00:11
Статья хорошая, но для новичков сложновато. Может, добавите больше сравнений 'до' и 'после' оптимизации?
👤
viktor.gromov
04.04.2026 01:52
Спасибо за объяснение, но хотелось бы больше примеров с мемоизацией вычислений в компонентах форм.
👤
pavel.novikov-web
04.04.2026 07:08
Отличная статья! Как раз столкнулся с проблемой лишних ререндеров в большом проекте. useCallback реально помог.