Урок по треку React 8 мин

memo, useMemo, useCallback и React Compiler

Когда мемоизация помогает, когда вредит и что делает React Compiler.

Откуда лишние рендеры

Когда компонент рендерится, React по умолчанию рендерит всех его потомков. Обычно это дёшево: рендер это вызов функций, а DOM меняется только там, где изменился результат. Проблемы начинаются, когда поддерево большое или в нём есть тяжёлые вычисления.

Прежде чем оптимизировать, измерьте: вкладка Profiler в React DevTools показывает, какие компоненты рендерились, почему и сколько это заняло. Замеряйте на продакшен-сборке, dev-режим заметно медленнее.

Сначала структура, потом мемоизация

Два приёма часто убирают проблему без единого memo.

Опустить state вниз. Если state нужен только полю ввода, вынесите поле с его state в отдельный компонент. Тогда ввод рендерит только этот компонент, а не всю страницу.

Передать тяжёлое как children. Элементы в children создаёт родитель. Когда меняется state обёртки, children остаётся той же ссылкой, и React его не рендерит заново.

function ScrollTracker({ children }) {
  const [y, setY] = useState(0);
  // ... подписка на scroll
  return <div>{y}{children}</div>; // children не перерендерятся при смене y
}
<ScrollTracker><HeavyTree /></ScrollTracker>

memo

memo(Component) пропускает рендер, если все props равны прошлым по Object.is (поверхностное сравнение). Работает, только если props действительно стабильны:

const Row = memo(function Row({ item, onSelect }) { ... });

<Row item={item} onSelect={() => select(item.id)} /> // новая функция каждый рендер: memo бесполезен

Ломают memo: стрелочные функции и объекты-литералы в props, style={{...}}, JSX в children. Изменение context или собственного state рендерит memo-компонент в любом случае.

useMemo и useCallback

useMemo(() => compute(a, b), [a, b]) кеширует результат и пересчитывает его, только если изменились зависимости.

useCallback(fn, deps) кеширует саму функцию. Это то же, что useMemo(() => fn, deps).

Когда это имеет смысл:

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

useCallback без memo у получателя ничего не даёт: функция всё равно создаётся, а ребёнок рендерится. Мемоизация не бесплатна: сравнение зависимостей и хранение кеша на каждом рендере, плюс шум в коде.

Зависимости должны быть полными, иначе получите устаревшее значение.

React Compiler

React Compiler (стабильная версия 1.0 с октября 2025) это плагин сборки, который автоматически мемоизирует компоненты и значения внутри них. С ним ручные memo, useMemo и useCallback в большинстве случаев не нужны. Условие: код соблюдает правила React (чистый рендер, без мутаций props и state, правила хуков). Компоненты, где правила нарушены, компилятор пропускает. В Next.js и Vite компилятор подключается в конфигурации.

Другие инструменты

  • Виртуализация длинных списков (TanStack Virtual, react-window): рендерить только видимые строки.
  • useTransition и useDeferredValue: пометить тяжёлое обновление как несрочное, чтобы ввод оставался отзывчивым.
  • Ленивая загрузка через lazy и Suspense: меньше JavaScript на старте.

Что спросят на собесе

  • Почему компонент рендерится, если его props не поменялись.
  • Чем useMemo отличается от useCallback и когда они бесполезны.
  • Почему memo не сработал (инлайн-функция, объект, children).
  • Как найти причину медленного интерфейса.
  • Что делает React Compiler и нужна ли теперь ручная мемоизация.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.