Каждый фронтенд-разработчик хоть раз попадал в эту коварную ловушку. Дизайнеры приносят в Figma великолепный интерфейс фильтрации каталога: аккуратный выпадающий список (Select) с поиском, группировкой по категориям, красивыми анимациями раскрытия и мягкими тенями. Ты берешь готовую дизайн-систему — например, Material UI (MUI), Mantine или Ant Design — подключаешь компонент за пять минут, проверяешь на тестовом массиве из пяти элементов: всё летает, анимация плавная, заказчик доволен.
А затем приложение выходит в продакшен, с бэкенда прилетает реальный справочник номенклатуры на 2000 позиций, пользователь кликает по селекту — и вкладка браузера намертво замирает на две секунды. Курсор превращается в колесико ожидания, вентиляторы ноутбука начинают выть, а разгневанный клиент строчит жалобу в техподдержку.
Почему простые интерфейсные компоненты так катастрофически деградируют под нагрузкой? Ответ на этот вопрос дал французский инженер Ромен Клеман (Romain Clément), опубликовавший подробнейшее исследование производительности ведущих дизайн-систем, освещенное в выпуске React Status и в открытом бенчмарке компонентов.
Давай разберем результаты этих замеров и поймем, какие архитектурные компромиссы скрываются за красивыми фасадами библиотек.
Методология стресс-теста: измеряем реальные задержки
Клеман подошел к тестированию с предельной инженерной строгостью. Вместо синтетических абстрактных тестов он создал воспроизводимый тестовый стенд на базе автоматизированного фреймворка Playwright.
Условия эксперимента:
- Тестовый стенд рендерит типовой выпадающий список на 2500 элементов с текстовыми метками и иконками.
- Имитируется пользовательский клик для открытия выпадающего меню.
- Через Performance API и трассировку Chrome DevTools замеряются ключевые метрики:
- TBT (Total Blocking Time): суммарное время блокировки основного потока браузера JavaScript-вычислениями;
- Время первого рендеринга DOM (Mount Duration);
- Потребление оперативной памяти кучей V8 (JS Heap Size).
Давай посмотрим, как выглядел базовый тестовый сценарий Playwright для фиксации задержек открытия меню:
import { test, expect } from '@playwright/test';
test('Замер времени блокировки потока при открытии селекта на 2500 элементов', async ({ page }) => {
await page.goto('/benchmark/select');
// Начинаем сбор метрик производительности через Chrome DevTools Protocol
const client = await page.context().newCDPSession(page);
await client.send('Performance.enable');
const startMark = performance.now();
// Кликаем по триггеру выпадающего списка
await page.click('[data-testid="framework-select-trigger"]');
// Ожидаем фактического появления выпадающего контейнера в DOM
await page.waitForSelector('[data-testid="framework-select-dropdown"]');
const renderTime = performance.now() - startMark;
console.log(`Время раскрытия меню: ${renderTime.toFixed(2)} мс`);
const metrics = await client.send('Performance.getMetrics');
const jsHeapMetric = metrics.metrics.find(m => m.name === 'JSHeapUsedSize');
console.log(`Память кучи JS: ${(jsHeapMetric!.value / 1024 / 1024).toFixed(2)} МБ`);
expect(renderTime).toBeLessThan(500); // Ожидаемый порог отзывчивости
});
Результаты замеров: где спотыкаются гиганты
Цифры бенчмарка оказались шокирующими даже для опытных архитекторов интерфейсов:
- MUI (Material UI v6):
- Время рендеринга меню: 1840 мс.
- Потребление памяти кучи: 48 МБ.
- Причина деградации: использование тяжелого CSS-in-JS движка (Emotion/Pigment). На каждый из 2500 пунктов списка MUI создает отдельное виртуальное дерево стилей, высчитывает классы в рантайме и регистрирует десятки слушателей событий.
- Ant Design (v5):
- Время рендеринга меню: 620 мс.
- Потребление памяти кучи: 28 МБ.
- Причина: библиотека использует встроенную виртуализацию списков по умолчанию, однако накладные расходы на анимации и расчет доступности все равно вызывают кратковременный фриз основного потока.
- Mantine (v7):
- Время рендеринга меню: 410 мс.
- Потребление памяти: 19 МБ.
- Отказ от CSS-in-JS в пользу нативных CSS-модулей в седьмой версии позволил Mantine существенно обойти конкурентов по скорости монтирования.
- Легковесные Headless-примитивы (Base UI / Radix с виртуализацией TanStack Virtual):
- Время рендеринга меню: 42 мс.
- Потребление памяти: 6 МБ.
Почувствуй колоссальную разницу: разрыв между классическим MUI и связкой headless-компонентов с виртуализацией составил более чем 40 раз по времени задержки интерфейса!
Архитектурный рецепт: как победить тормоза
Если в твоем проекте уже используется тяжелая дизайн-система, переписывать всё приложение с нуля обычно нереально. Но вылечить тормоза в критических местах можно с помощью двух проверенных шагов:
Во-первых, виртуализация. Никогда не позволяй браузеру рендерить в реальное DOM-дерево больше 20–30 видимых элементов списка одновременно:
import { useVirtualizer } from '@tanstack/react-virtual';
import React, { useRef } from 'react';
export function VirtualizedOptionsList({ items }: { items: string[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 36, // Высота одного пункта списка в пикселях
overscan: 5, // Запас элементов сверху и снизу
});
return (
<div ref={parentRef} className="h-64 overflow-auto border rounded">
<div style={{ height: `${rowVirtualizer.getTotalSize()}px`, position: 'relative' }}>
{rowVirtualizer.getVirtualItems().map((virtualRow) => (
<div
key={virtualRow.index}
className="absolute top-0 left-0 w-full px-3 py-2 hover:bg-slate-100 cursor-pointer"
style={{ transform: `translateY(${virtualRow.start}px)` }}
>
{items[virtualRow.index]}
</div>
))}
</div>
</div>
);
}
Во-вторых, отказ от рантайм CSS-in-JS на тяжелых списках в пользу Tailwind CSS или чистых CSS-модулей. Если вспомнить наш анализ того, как происходит архитектурная эрозия в веб-системах, слепое доверие популярным библиотекам без замера их поведения под нагрузкой — верный путь к потере пользователей.
Красивый дизайн обязан быть быстрым. Замеряйте производительность компонентов до релиза в продакшен, а не после жалоб пользователей!
