Производительность frontend — это меньше лишней работы: лишних перерисовок и лишнего кода, который грузит браузер. Но главная ошибка тут — оптимизировать наугад: обвешать всё useMemo «на всякий случай», усложнив код и ничего не ускорив. Правило одно: сначала измерить, потом оптимизировать, и оптимизировать то, что реально медленно.
Перерисовки и преждевременная оптимизация
React перерисовывает компонент при изменении его состояния или пропсов — и большинство таких перерисовок дёшевы. Перерисовка ≠ перерисовка DOM: React сравнивает результат и трогает реальный DOM только там, где есть разница. Поэтому борьба с каждой перерисовкой обычно не нужна и вредна — она добавляет код и баги ради выигрыша, которого нет.
Признак реальной проблемы — заметная задержка ввода или прокрутки, видимая в профайлере, а не «компонент рендерится N раз». Профилировать — React DevTools Profiler: он показывает, что и почему рендерится и сколько это стоит.
memo, useMemo, useCallback — по делу
Эти инструменты нужны точечно, когда профайлер показал проблему.
React.memo — пропускает перерисовку компонента, если пропсы не изменились по ссылке. Помогает для дорогого компонента, которому часто приходят те же пропсы.
useMemo — кеширует результат дорогого вычисления между рендерами.
const sorted = useMemo(
() => products.slice().sort((a, b) => a.price - b.price),
[products],
);
useCallback — стабилизирует ссылку на функцию, чтобы не ломать React.memo-потомка или массив зависимостей эффекта.
Когда они вредят: на дешёвых вычислениях useMemo стоит дороже самого вычисления; useCallback без memo-потомка — просто шум; обмемоизированный код труднее читать. Эвристика: не добавляй мемоизацию, пока профайлер не показал, что без неё медленно.
Code-splitting
Второй рычаг — не грузить сразу весь код. React.lazy + Suspense подгружают компонент, когда он понадобился; чаще всего делят по маршрутам.
const Dashboard = React.lazy(() => import("./Dashboard"));
<Suspense fallback={<Spinner />}>
<Dashboard />
</Suspense>
Так первый экран грузит только свой код, а тяжёлые разделы (редактор, графики) подтягиваются по требованию. Это прямо ускоряет первую загрузку — метрику, которую чувствует пользователь.
Размер бандла
Самая частая причина медленного старта — раздутый бандл. Лечится измерением (анализатор бандла показывает, что весит) и дисциплиной: не тащить тяжёлую зависимость ради одной функции, делить код, убирать неиспользуемое. Одна большая библиотека, утянутая целиком, нередко весит больше всего твоего кода.
Принцип тот же, что и с рендером: измеряй (анализатор, Lighthouse), оптимизируй найденное, а не воображаемое.
Где это в UCP
Производительность — инженерное решение по измерению, а не по интуиции: большинство перерисовок дёшевы, мемоизация — точечно по профайлеру, code-splitting и контроль бандла — за быструю первую загрузку. Это то же «не оптимизируй преждевременно, профилируй перед оптимизацией», что в backend. Для продукт-инженера скорость интерфейса — часть качества продукта, которую он держит измеримо; а закрепляется всё это тестами, проверяющими поведение, а не детали оптимизаций.