Irbisa · Шпаргалка

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

React.memo, useMemo и useCallback, React Compiler, разбиение кода и lazy, виртуализация списков, Profiler, Core Web Vitals, размер бандла

Senior-разработчик · 20 вопросов

  1. 01

    Какими основными способами можно ускорить React-приложение и в каком порядке их применять?

    Сначала измерить, потом сокращать работу в таком порядке: отправлять меньше JavaScript (code splitting, более лёгкие зависимости), меньше рендерить (стейт пониже в дереве, виртуализация списков, мемоизация там, где Profiler показывает лишнюю работу) и держать главный поток свободным для ввода (transitions, отложенные значения).

  2. 02

    Что делает React.memo и когда мемоизированный компонент всё равно ререндерится?

    React.memo оборачивает компонент так, что React пропускает его ререндер, если родитель перерисовался с пропсами, поверхностно равными прежним (Object.is для каждого пропса).

    interface RowProps {  label: string;  onSelect: (label: string) => void;} const Row = memo(function Row({ label, onSelect }: RowProps) {…
  3. 03

    Найдите баг: Chart обёрнут в memo, но Profiler показывает, что он ререндерится на каждое нажатие клавиши в поиске.

    const Chart = memo(function Chart({ options, onPointClick }: ChartProps) {  return <canvas data-type={options.type} onClick={() => onPointClick(0)} />;}); function Dashboard({ data }: { data: number[] }) {  const [query, setQuery] = useState('');  return (    <>      <input value={query} onChange={(e) => setQuery(e.target.value)} />      <Chart        options={{ type: 'line', data }}        onPointClick={(i) => console.log(data[i])}      />    </>  );}

    Dashboard при каждом рендере создаёт новый объект options и новую стрелочную функцию onPointClick, поэтому поверхностное сравнение в memo всегда видит изменённые пропсы и никогда не пропускает рендер.

  4. 04

    Чего стоит мемоизация и когда React.memo, useMemo или useCallback делают только хуже?

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

  5. 05

    Как избавиться от лишних ререндеров без мемоизации: опустить стейт ниже или передать компоненты через children?

    Обновление стейта ререндерит только компонент, которому этот стейт принадлежит, и его потомков, поэтому часто меняющийся стейт стоит держать в самом маленьком компоненте, которому он нужен.

    // before: every scroll event re-renders ExpensiveTreefunction SlowPage() {  const [scrollY, setScrollY] = useState(0);  return (    <div onScroll={(e) => setScrollY(e.currentTarget.scrollTop)}>      <Header shadow={scrollY > 0} />…
  6. 06

    Что такое React Compiler и делает ли он ненужными useMemo, useCallback и React.memo?

    React Compiler (стабилен с версии 1.0 в октябре 2025) представляет собой Babel-плагин времени сборки, который анализирует компоненты и хуки и автоматически расставляет точечную мемоизацию.

  7. 07

    Как с помощью React DevTools Profiler узнать, какие компоненты ререндерятся и почему?

    Запишите медленное взаимодействие в React DevTools Profiler и разберите flame graph каждого коммита: серые полосы не рендерились, цветные рендерились, и для каждой видно время.

    function onRender(  id: string,  phase: 'mount' | 'update' | 'nested-update',  actualDuration: number,  baseDuration: number,) {…
  8. 08

    Что такое code splitting и как React.lazy и Suspense загружают компонент по требованию?

    Code splitting разбивает бандл на чанки, которые загружаются по мере надобности, а не сразу.

    const SettingsPage = lazy(() => import('./SettingsPage'));const preloadSettings = () => import('./SettingsPage'); export function App() {  const [open, setOpen] = useState(false);  return (…
  9. 09

    Найдите баг: ленивый редактор показывает fallback и теряет весь введённый текст при каждом ререндере родителя.

    function NotePage({ noteId }: { noteId: string }) {  const [saved, setSaved] = useState(false);  const Editor = lazy(() => import('./Editor'));   return (    <Suspense fallback={<p>Loading editor...</p>}>      <Editor noteId={noteId} onSave={() => setSaved(true)} />      {saved && <p>Saved</p>}    </Suspense>  );}

    lazy вызывается внутри компонента, поэтому каждый рендер создаёт новый тип компонента.

  10. 10

    Найдите баг: график подгружается через lazy, но вся библиотека графиков всё равно лежит в основном бандле.

    // Dashboard.tsximport { lazy, Suspense, useState } from 'react';import { CHART_COLORS } from './RevenueChart'; const RevenueChart = lazy(() => import('./RevenueChart')); export function Dashboard() {  const [show, setShow] = useState(false);  return (    <div style={{ borderColor: CHART_COLORS.primary }}>      <button onClick={() => setShow(true)}>Show revenue</button>      {show && (        <Suspense fallback={<p>Loading chart...</p>}>          <RevenueChart />        </Suspense>      )}    </div>  );}

    Dashboard ещё и статически импортирует CHART_COLORS из ./RevenueChart, поэтому этот модуль вместе с библиотекой графиков, которую он импортирует, обязан оказаться в одном чанке с Dashboard.

  11. 11

    Что такое виртуализация списка (windowing), когда она нужна и как работает?

    Виртуализация рендерит только строки, видимые в области прокрутки, плюс небольшой запас (overscan), и размещает их внутри распорки высотой во весь список.

    function LogList({ lines }: { lines: string[] }) {  const parentRef = useRef<HTMLDivElement>(null);  const virtualizer = useVirtualizer({    count: lines.length,    getScrollElement: () => parentRef.current,    estimateSize: () => 24,…
  12. 12

    Что такое Core Web Vitals (LCP, INP, CLS) и что в React-приложении обычно ухудшает каждую из метрик?

    Core Web Vitals представляют собой три полевые метрики Google, которые оценивают по 75-му перцентилю реальных визитов: LCP (показ основного контента, хорошо до 2,5 с), INP (реакция на ввод, хорошо до 200 мс, в 2024 году заменила FID) и CLS (стабильность вёрстки, хорошо до 0,1).

  13. 13

    Что делают <link rel="preload">, prefetch и preconnect, и как их используют API preload и preinit из React 19?

    Они заранее сообщают браузеру о запросах, которые он нашёл бы позже: preload загружает файл, нужный текущей странице, prefetch загружает файл, который может понадобиться при следующей навигации, с самым низким приоритетом, а preconnect заранее открывает соединение с origin.

    import { preconnect, prefetchDNS, preinit, preload } from 'react-dom';import { Link } from 'react-router'; export function CheckoutPage() {  // called during render: with SSR they go out in the HTML before the form needs them  preconnect('https://api.payments.example');…
  14. 14

    Как загружать веб-шрифты без невидимого текста и сдвигов вёрстки?

    Показывайте текст запасным шрифтом сразу с font-display: swap (или optional), получайте файл шрифта пораньше, размещая у себя небольшой WOFF2 и предзагружая его, и подгоняйте метрики запасного шрифта через size-adjust, чтобы подмена не сдвигала текст.

    /* in <head>, for the one file used above the fold:   <link rel="preload" href="/fonts/inter-latin.woff2" as="font" type="font/woff2" crossorigin> */ @font-face {  font-family: 'Inter';  src: url('/fonts/inter-latin.woff2') format('woff2');…
  15. 15

    Когда в React-приложении стоит переносить работу в Web Worker, и как с ним общаться?

    Когда чистые вычисления блокируют главный поток дольше примерно 50 мс: разбор большого CSV или JSON, сортировка или фильтрация десятков тысяч строк, построение поискового индекса, обработка изображения.

    // sort.worker.ts: runs on its own thread, no DOM and no Reactself.onmessage = (e: MessageEvent<Row[]>) => {  const sorted = [...e.data].sort(compareRows); // the 300 ms part  self.postMessage(sorted);};…
  16. 16

    Почему клик или ввод в React-приложении ощущается медленным (плохой INP) и как разбить долгую работу рендера?

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

    function ReportPage({ rows }: { rows: Row[] }) {  const [tab, setTab] = useState<'summary' | 'details'>('summary');  const [isPending, startTransition] = useTransition();   function selectTab(next: 'summary' | 'details') {    // the heavy tab renders in interruptible slices…
  17. 17

    Что делает компонент <Activity>, появившийся в React 19.2, и как он ускоряет навигацию?

    <Activity mode="hidden"> скрывает поддерево, не размонтируя его: React сохраняет его стейт и DOM, выполняет очистку эффектов и рендерит обновления для него с самым низким приоритетом.

    function Tabs({ tab }: { tab: 'feed' | 'profile' }) {  return (    <>      <Activity mode={tab === 'feed' ? 'visible' : 'hidden'}>        <Feed />      </Activity>…
  18. 18

    Как измерять производительность React-приложения в продакшене, а не на своей машине?

    Собирать метрики реальных пользователей (RUM): библиотека web-vitals сообщает LCP, INP и CLS из браузера каждого посетителя, вы отправляете их через navigator.sendBeacon на эндпоинт аналитики и следите за 75-м перцентилем по страницам и классам устройств.

    import { onCLS, onINP, onLCP, type Metric } from 'web-vitals'; function send(metric: Metric) {  const body = JSON.stringify({    name: metric.name,    value: metric.value,…
  19. 19

    Как понять, из-за чего React-бандл получился большим, и как его уменьшить?

    Сначала измерить визуализатором бандла (rollup-plugin-visualizer для Vite, webpack-bundle-analyzer или source-map-explorer по собранным source map), потом убрать самое крупное: тяжёлые зависимости, код, которому место в ленивых чанках, и код, который не удалил tree shaking.

  20. 20

    Таблица на 5000 строк зависает при каждом нажатии клавиши в поле фильтра. Расскажите, как вы будете искать причину и исправлять.

    Воспроизвести с замедлением CPU, записать нажатие клавиши в React Profiler и панели Performance, чтобы понять, уходит ли время на рендер React, на layout или на собственный код, и исправить самое дорогое; обычно это рендер всех 5000 строк на каждое нажатие.