Профилирование React-приложения: ищем узкие места
Быстро находите узкие места в React-приложении с профилированием, чтобы ваш UI оставался отзывчивым.
Если ваш список таблицы начал “тормозить”, а пользовательские действия стали заметно медленнее скорее всего, проблема скрывается в лишних рендерах или тяжелом расчете в UI-слое. Профилирование React-приложения позволяет увидеть узкие места в реальном времени и быстро принять решение, какие части кода нуждаются в “похолодании”. Профилирование React-приложения ваш первый шаг к измеримому улучшению производительности.
Обычные unit-тесты тут мало что скажут, потому что они проверяют только бизнес-логику, а не то, сколько времени тратит браузер на отрисовку. Профилирование React-приложения позволяет увидеть узкие места в реальном времени и быстро принять решение, какие части кода нуждаются в “похолодании”.
1. Почему стандартные метрики не работают для UI-оптимизации
В большинстве проектов измеряют производительность через показатели Time To First Paint или Largest Contentful Paint. Они дают общую картину, но не показывают, какие компоненты именно “залипают”. React-приложения часто страдают от избыточных ререндеров каждый рендер добавляет миллисекунды, а в сумме они становятся ощутимыми.
Tip: Если вы используете Redux, проверьте, не подписаны ли ваши компоненты на слишком большую часть стейта. Чтение большого дерева может спровоцировать лишние обновления.
2. Инструменты профилирования в арсенале фронтендера
Инструмент | Что измеряет | Как включить |
---|---|---|
Chrome DevTools → Performance | Общие тайминги, JavaScript, Layout, Paint | Ctrl+Shift+I → Performance → Record |
React Profiler (React DevTools) | Количество ререндеров, длительность их выполнения | Включить вкладку Profiler в React DevTools |
why-did-you-render (npm) | Подсказки о нежелательных рендерах | npm i @welldone-software/why-did-you-render |
React Profiler ваш главный помощник в профилировании React-приложения. Он визуализирует рендер-дерево в виде Flamegraph, где каждый бар отдельный рендер компонента. Чем шире бар, тем дольше шел рендер.
Ссылка: Подробнее о работе Profiler можно почитать в React Profiler.
3. Как измерять рендеры программно
Для программного измерения рендеров в профилировании React-приложения React предоставляет API Profiler
как компонент-обертку. Его колбэки позволяют собрать метрики в любой момент:
import { Profiler } from "react";
function onRender(
id: string, // имя Profiler
phase: "mount" | "update",
actualDuration: number, // ms, затраченные на рендер
baseDuration: number, // ms, без оптимизаций
startTime: number,
commitTime: number
) {
console.log(`[${id}] ${phase} ${actualDuration.toFixed(2)}ms`);
}
export default function App() {
return (
<Profiler id="AppRoot" onRender={onRender}>
<HeavyComponent />
</Profiler>
);
}
Собранные данные можно отправлять в Loki/Elastic для последующего анализа. Главное фиксировать actualDuration и сравнивать ее между версиями.
4. Поиск “тяжелых” компонентов и memo-оптимизации
После того как профайлер показал, какие компоненты рендерятся чаще всего, следующий шаг снизить их количество. Самый простой способ использовать мемоизацию.
import React, { memo } from "react";
function ListItem({ item }) {
console.log("render", item.id);
return <li>{item.title}</li>;
}
export default memo(ListItem); // запоминает результат, если props не изменились
memo
работает как PureComponent
для функциональных компонентов: если пропсы одинаковы рендер пропускается. При сложных вычислениях в пропсах стоит добавить useMemo
или useCallback
.
Ссылка: Подробнее о мемоизация компонентов поможет понять, когда стоит применять
memo
.
Частая ошибка
Не переусердствуйте: оборачивать каждый компонент в memo
без анализа приводит к медленному сравнению props, особенно если они глубоко вложены. Лучше сначала профилировать, а потом мемоизировать.
5. Когда “много элементов” дело виртуализации списка
Если узкое место связано с рендером огромного списка (например, таблица из 10 000 строк), обычные оптимизации не спасут. Здесь на сцену выходит виртуализация рендерятся только те элементы, которые видимы в viewport.
import { FixedSizeList as List } from "react-window";
function Row({ index, style }) {
const item = data[index];
return <div style={style}>{item.title}</div>;
}
export default function VirtualizedTable() {
return (
<List height={600} itemCount={data.length} itemSize={35} width="100%">
{Row}
</List>
);
}
react-window
(или react-virtualized
) экономит ресурсы, сокращая количество DOM-узлов. После внедрения виртуализации в профайлере обычно наблюдается резкое падение actualDuration у родительского списка.
Ссылка: Пример виртуализации в действии смотрите виртуализацию списка.
6. Чеклист для быстрой отладки
- Проверьте количество ререндеров в React Profiler. Если один компонент участвует в более чем 30% всех рендеров ищите причину.
- Отключите ненужные подписки на Redux/Context, чтобы снизить частоту обновлений стейта.
- Добавьте
memo
только после профайлинга. Не используйте ее везде. - Виртуализируйте большие списки сразу, если их длина превышает 500-1000 элементов.
- Не забывайте про
useCallback
при передаче функций вниз по дереву иначе каждый рендер будет создавать новый объект, ломая мемоизацию.
Эти пять пунктов покрывают 80 % типичных проблем с производительностью в React-проектах.
Наблюдать “тормоза” в продакшене страшно, но правильно настроенное профилирование React-приложения позволяет pinpoint-ировать узкие места и быстро их исправлять. При возникновении новых лагов просто запустите Profiler, посмотрите Flamegraph и применяйте описанные приемы. Пора перестать гадать и начать измерять.