Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Производительность и React Compiler
performance_compiler

Производительность и React Compiler

Profiler, React Compiler 1.0, мемоизация, виртуализация, разделение кода, Web Vitals и доказуемая оптимизация.

Открыть лабораториюv0.2.0Запускается локально из публичного репозитория

Производительность и React Compiler

Оптимизация начинается с пользовательской метрики и воспроизводимой трассировки, а заканчивается повторным измерением.

#Результат урока

Вы найдёте узкое место, отличите вычисление React от фиксации DOM, сети и разметки, включите React Compiler и докажете улучшение без регрессии.

Понадобятся модель отрисовки, настройка React Compiler, Suspense и загрузка в браузере. Итог урока — отчёт до и после для тяжёлой панели с бюджетом производительности и регрессионным тестом.

#1. Исходное измерение и целевая метрика

Зафиксируйте устройство, сборку, набор данных и действие. Метрика должна отражать пользовательский опыт: LCP, INP, CLS, смену маршрута или задержку ввода. Среднее скрывает худшие случаи; смотрите распределение и p75 вместе с лабораторной трассировкой и данными реальных пользователей (RUM).

Исходное измерение — это запись, к которой можно вернуться через месяц и повторить её один в один:

Сценарий: /dashboard, ввод в поиск, 5 000 строк в наборе Сборка: production, React 19.2, Compiler выключен Условия: CPU throttling 4x, Fast 3G, окно 1280×800 Метрика: INP p75 = 420 мс (цель ≤ 200 мс), LCP p75 = 2.9 с Данные: 20 повторов, отброшены первые 2 прогона

Без записанных условий сравнение «до и после» ничего не доказывает: разница может объясняться другой машиной, другим набором данных или прогретым кешем. Один console.time даёт единичное наблюдение из режима разработки — то есть измеряет сборку, которую пользователь никогда не увидит.

Проверьте себя. Запишите исходное значение p75 и точный сценарий.

Частая ошибка. Оптимизировать по одному случайному console.time.

#2. Profiler: причина, длительность, фиксация

React DevTools Profiler показывает фиксации, компоненты и причины обновлений. <Profiler> даёт программный обратный вызов. Режим разработки отличается от рабочей сборки, поэтому используйте сборку для профилирования и реальные трассировки. Сначала найдите дорогую ветвь и частоту вызовов, затем разбирайте владельца данных и свойства.

Программный обратный вызов полезен, когда нужно измерять сценарий автоматически, а не глазами:

// src/app/dashboard/DashboardProfiler.tsx <Profiler id="dashboard" onRender={(id, phase, actualDuration, baseDuration) => { // actualDuration — фактическая работа, baseDuration — оценка без мемоизации reportRenderMetric({ id, phase, actualDuration, baseDuration }); }} > <Dashboard /> </Profiler>

Число вызовов компонента само по себе ничего не значит: сто вызовов по 0.05 мс дешевле одного вызова на 30 мс. Смотреть надо на длительность фиксации и на то, какая ветвь в неё вносит вклад. phase при этом отличает первое монтирование от обновления — оптимизировать их приходится по-разному.

Проверьте себя. Найдите нестабильное значение провайдера.

Частая ошибка. Судить только по числу вызовов компонента без длительности.

#3. Compiler и чистый код

React Compiler автоматически мемоизирует код во время сборки, если соблюдаются правила React. Он не исправляет последовательные сетевые запросы, огромный DOM или чередование чтения и записи разметки. Диагностика помогает найти мутации и нечистые вычисления. Версию фиксируют при постепенном внедрении и обновляют вместе со сквозными тестами.

Включение — это настройка сборки, а не правка компонентов:

// vite.config.ts export default defineConfig({ plugins: [ react({ babel: { plugins: [['babel-plugin-react-compiler', { target: '19' }]] }, }), ], });

Но компилятор не тронет код, нарушающий правила React, — и это к лучшему, потому что такой код нельзя мемоизировать безопасно:

// ❌ компилятор пропустит компонент: свойство мутируется во время отрисовки function Row({ item }: { item: Item }) { item.viewedAt = Date.now(); // мутация входных данных return <td>{item.title}</td>; }

Отчёт компилятора («bailout») — это список мест, где нарушена чистота: мутация свойств, чтение и запись ref во время отрисовки, обращение к изменяемой внешней переменной. Их исправляют, а не обходят. И ни одна мемоизация не ускорит страницу, которая ждёт четыре последовательных запроса.

Проверьте себя. Сравните результат и трассировку скомпилированного поддерева.

Частая ошибка. Считать React Compiler универсальным исправлением производительности.

#4. Ручная мемоизация для узких случаев

memo пропускает вызов компонента при равных свойствах, useMemo кеширует вычисление, useCallback сохраняет ссылку на функцию. Кеш не делает вычисление корректным и сам имеет стоимость. Стабильность нужна для дорогого потомка или внешней зависимости, а не ради отключения предупреждения линтера.

Самая частая бесполезная мемоизация выглядит так: memo есть, но каждый новый объект в свойствах его отменяет.

// ❌ memo не срабатывает: config и onSelect новые на каждой отрисовке родителя const HeavyTable = memo(Table); <HeavyTable config={{ dense: true }} onSelect={(id) => setSelected(id)} />

Стабилизировать нужно именно то, что участвует в сравнении:

// ✅ значения стабильны между отрисовками const config = useMemo(() => ({ dense: true }), []); const handleSelect = useCallback((id: string) => setSelected(id), []); <HeavyTable config={config} onSelect={handleSelect} />

Ещё лучше — вынести константу за пределы компонента: тогда мемоизация не нужна вовсе. И помните, что сам memo не бесплатен: поверхностное сравнение свойств дешёвого компонента может стоить дороже его повторного вызова.

Проверьте себя. Измерьте мемоизированного потомка до и после стабилизации свойств.

Частая ошибка. Новый объект в свойствах делает memo бесполезным.

#5. Виртуализация большого списка

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

Виртуализатор отдаёт индексы видимых элементов и общую высоту, а разметку строите вы:

// src/features/tickets/VirtualTicketList.tsx const virtualizer = useVirtualizer({ count: tickets.length, getScrollElement: () => scrollRef.current, estimateSize: () => 48, overscan: 8, // запас, чтобы прокрутка не мигала пустотой }); <div ref={scrollRef} style={{ height: 600, overflow: 'auto' }}> <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}> {virtualizer.getVirtualItems().map((row) => ( <div key={tickets[row.index].id} /* ключ доменный, не row.index */ style={{ position: 'absolute', top: row.start, height: row.size, width: '100%' }} > <TicketRow ticket={tickets[row.index]} /> </div> ))} </div> </div>

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

Проверьте себя. Измерьте число DOM-узлов и прокрутку списка из 10 000 строк.

Частая ошибка. Виртуализировать список из 20 строк.

#6. Пакет, его части и предварительная загрузка

Измеряйте переданные сжатые байты, разбор, выполнение и последовательность запросов. Разделение по маршрутам и возможностям уменьшает начальный JavaScript, но слишком мелкие части добавляют запросы. preload оставляйте для скоро нужного критичного ресурса, prefetch — для вероятного будущего. Удаление неиспользуемого кода (tree shaking) зависит от ESM и sideEffects.

Тяжёлую и редкую часть интерфейса выносят в отдельный фрагмент:

// src/features/reports/ReportChart.tsx const Chart = lazy(() => import('./HeavyChart')); // ~180 КБ уходят из начального пакета <Suspense fallback={<ChartSkeleton />}> <Chart data={data} /> </Suspense>

Импорт целой библиотеки ради одной функции работает в обратную сторону — и sideEffects в её package.json может помешать удалить неиспользуемое:

// ❌ в пакет попадает вся библиотека import _ from 'lodash'; const unique = _.uniqBy(items, 'id'); // ✅ либо точечный импорт, либо своя короткая функция import uniqBy from 'lodash-es/uniqBy';

Разделение стоит проверять по факту: анализатор пакета показывает, что действительно попало в начальный фрагмент, а панель сети — сколько запросов добавилось. Сто мелких частей по 2 КБ хуже одной на 200 КБ.

Проверьте себя. Проанализируйте пакет и лениво загружаемую диаграмму.

Частая ошибка. Импортировать целую библиотеку утилит ради одной функции.

#7. Разметка, отрисовка браузера и CLS

Компоненты React могут вычисляться быстро, а браузерная разметка — медленно из-за DOM и CSS. Резервируйте размеры изображений, не чередуйте принудительное чтение и запись разметки (layout thrashing), осознанно анимируйте transform и opacity. CLS измеряет неожиданные сдвиги, а не любое движение после действия пользователя.

Чередование чтения и записи внутри одного цикла заставляет браузер пересчитывать разметку на каждой итерации:

// ❌ N принудительных пересчётов разметки rows.forEach((row) => { const height = row.offsetHeight; // чтение — требует актуальной разметки row.style.height = `${height + 8}px`; // запись — делает разметку устаревшей }); // ✅ сначала все чтения, потом все записи const heights = rows.map((row) => row.offsetHeight); rows.forEach((row, index) => { row.style.height = `${heights[index] + 8}px`; });

Сдвиг разметки от изображений устраняется резервированием места до загрузки:

<img src={cover} alt="" width={320} height={180} style={{ aspectRatio: '16 / 9' }} />

Без width, height или aspect-ratio браузер не знает размер до загрузки файла и подставляет нулевую высоту — весь текст под картинкой прыгает вниз в момент её появления.

Проверьте себя. Уберите сдвиг разметки у изображений карточек.

Частая ошибка. Изображение без width/height/aspect-ratio.

#8. Бюджеты и защита от регрессии

Бюджет производительности задаёт пороги размера пакета, задержки маршрута, Web Vitals и критического взаимодействия в контролируемой среде. Лабораторная метрика в CI шумна, поэтому нужны допуски и динамика; наблюдение в эксплуатации показывает реальные устройства. Не блокируйте слияние по одному нестабильному запуску.

Бюджет размера удобно держать в конфигурации сборки — он проверяется на каждом прогоне и не требует ручной дисциплины:

{ "budgets": [ { "path": "dist/assets/index-*.js", "maxSize": "180 kB", "compression": "gzip" }, { "path": "dist/assets/vendor-*.js", "maxSize": "240 kB", "compression": "gzip" } ] }

Метрики взаимодействия шумнее размера, поэтому их сравнивают по нескольким прогонам и с допуском:

// perf/interaction.spec.ts const runs = await measureInputLatency({ repeats: 7 }); expect(median(runs)).toBeLessThan(BASELINE_MS * 1.15); // допуск 15% на шум CI

Медиана по семи прогонам и явный допуск дают сигнал, на который можно опираться; порог по одному запуску даёт «мигающий» тест, который команда быстро научится игнорировать. Разовый аудит перед запуском не защищает вообще: регрессия придёт со следующим релизом.

Проверьте себя. Добавьте бюджет пакета и сохранение трассировки.

Частая ошибка. Ручной аудит один раз перед запуском.

#Лаборатория

Профилируйте панель: медленный селектор, нестабильный контекст, 10 000 строк, отдельную часть с диаграммой и сдвиг разметки. Включите React Compiler, исправьте владение данными, виртуализируйте список, лениво загрузите диаграмму и измерьте взаимодействие и Web Vitals.

Работа готова, когда есть исходное измерение, трассировка, гипотеза, изменение и повторное измерение, а тесты подтверждают прежнее поведение.

#Источники для сверки

  • React Compiler v1.0
  • React Profiler
  • web.dev Web Vitals

Устранение неисправностей

Далее: Безопасность React-приложений