Оптимизация производительности React: memo, useMemo и ререндеры
Продвинутое3 минРедакция Innovialabs
Коротко
Сначала измерьте в React Profiler, что именно тормозит; затем уберите причину лишних ререндеров структурой компонентов и только потом точечно добавляйте memo, useMemo и виртуализацию.
Короткий ответ
Ререндер сам по себе — не проблема: React рассчитан на частые перерисовки. Проблема — когда дорогие компоненты перерисовываются без нужды и это заметно пользователю. Порядок действий всегда один: измерить → найти причину → исправить структуру → мемоизировать точечно.
Почему компонент перерисовывается
Компонент рендерится заново, если:
- изменилось его состояние;
- перерисовался родитель (даже если пропсы те же);
- изменилось значение контекста, который он читает.
Второй пункт — главный источник лишних ререндеров. Большой компонент со состоянием наверху дерева тянет за собой всё, что ниже.
Как найти лишние ререндеры
- Установите React Developer Tools и откройте вкладку Profiler.
- Включите в настройках запись причин рендера («why did this render»).
- Запишите проблемное действие: ввод в поле, открытие списка, переключение фильтра.
- Смотрите на flamegraph: какие компоненты рендерились, сколько времени заняли и почему.
Проверяйте на production-сборке и при необходимости с замедлением CPU в DevTools браузера: в режиме разработки React работает медленнее и картина искажается.
Сначала структура, потом мемоизация
Многие проблемы решаются без memo:
- Опустите состояние вниз. Если поле поиска меняет состояние, держите его в маленьком компоненте
SearchInput, а не в странице целиком. - Передавайте тяжёлые части через children. Элементы, созданные снаружи и переданные как
children, не перерисовываются при изменении состояния обёртки. - Разделяйте контексты. Часто меняющиеся данные не должны лежать в одном контексте с редко меняющимися.
- Не создавайте компоненты внутри компонентов. Такой компонент пересоздаётся на каждом рендере и теряет состояние.
memo, useMemo и useCallback
| Инструмент | Что делает | Когда помогает |
|---|---|---|
React.memo | Пропускает рендер, если пропсы не изменились | Тяжёлый компонент часто получает те же пропсы |
useMemo | Кеширует результат вычисления | Дорогое вычисление или объект для мемо-компонента |
useCallback | Кеширует функцию | Функция передаётся в мемо-компонент или в зависимости эффекта |
const Row = memo(function Row({ item, onSelect }) {
return <li onClick={() => onSelect(item.id)}>{item.name}</li>;
});
function List({ items }) {
const [selected, setSelected] = useState(null);
const handleSelect = useCallback((id) => setSelected(id), []);
return items.map((item) => (
<Row key={item.id} item={item} onSelect={handleSelect} />
));
}
Без useCallback здесь handleSelect создавалась бы заново на каждом рендере и memo у Row не работал бы.
Когда мемоизация вредит
- Сравнение пропсов тоже стоит времени. Для лёгких компонентов оно может быть дороже самого рендера.
- Код усложняется, растёт риск ошибок в массивах зависимостей.
- Мемоизация ломается незаметно: достаточно передать новый объект
style={{...}}или стрелочную функцию — иmemoперестаёт срабатывать.
Добавляйте мемоизацию только там, где Profiler показал реальную проблему.
Виртуализация длинных списков
Если на экране тысячи строк, проблема не в ререндерах, а в количестве DOM-узлов. Виртуализация рендерит только видимые элементы и небольшой запас вокруг. Для этого есть библиотеки, например TanStack Virtual или react-window. Альтернатива — пагинация или подгрузка по мере прокрутки.
React Compiler
React Compiler — инструмент сборки от команды React, который автоматически мемоизирует компоненты и значения. Он избавляет от большей части ручных memo, useMemo и useCallback, но требует, чтобы код соблюдал правила React: чистые компоненты, отсутствие мутаций пропсов и состояния. Перед подключением проверьте совместимость с вашей версией React и фреймворком в официальной документации.
Другие частые причины тормозов
- Тяжёлые бандлы — используйте разделение кода и
lazy. - Синхронные вычисления на каждый ввод — помогают
useDeferredValueиuseTransition. - Большие несжатые изображения и анимации, вызывающие перерасчёт раскладки.