Главная / Статьи / Объяснение частичной предварительной обработки и одновременной обработки

Объяснение частичной предварительной обработки и одновременной обработки

Узнайте, как функция частичной предварительной обработки в Next.js и механизм одновременной обработки в React устраняют проблему медленной работы приложений, позволяя фреймворкам планировать и выполнять задачи поэтапно, вместо того чтобы рассматривать процесс отрисовки как единую блокирующую операцию.

1346 слов

Современные подходы к улучшению производительности в React и Next.js постоянно возвращаются к одной и той же мысли: именно принудительное поведение всей страницы или всего процесса отрисовки как единого, непрерывного целого делает приложения медленными. Два метода решают эту проблему с разных точек зрения — частичная предварительная отрисовка, позволяющая одному маршруту Next.js сочетать статический и динамический контент, и одновременная отрисовка, позволяющая React прерывать и переупорядочивать операции в браузере. Вместе они демонстрируют закономерность, которую стоит понимать: улучшения производительности всё чаще достигаются не за счёт написания «более быстрого» кода, а благодаря более умному планированию и выполнению задач фреймворком.

Старый выбор между полной или нулевой отрисовкой

Долгое время у страницы Next.js существовало ровно два варианта отрисовки:

  • Статическое генерирование (SSG) — страницы загружаются быстро, поскольку они создаются заранее, но их содержимое устаревает до следующей пересборки.
  • Отрисовка на стороне сервера (SSR) — содержимое всегда актуально, но каждый запрос вынужден ждать самых медленных данных перед тем, как что-либо будет отправлено обратно.

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

Возможность сделать один маршрут одновременно статическим и динамическим

Частичная предварительная обработка (PPR) существует для преодоления этой проблемы. Она позволяет одному маршруту сразу же отправлять статическую основу, в то время как динамические элементы внутри неё загружаются по мере получения данных — без отдельных страниц и без необходимости переключаться между getStaticProps и getServerSideProps.

// app/product/[id]/page.tsx
import { Suspense } from 'react';
import ProductShell from '@/components/ProductShell';
import LiveInventory from '@/components/LiveInventory';

export default function ProductPage({ params }: { params: { id: string } }) {
  return (
    <ProductShell productId={params.id}>
      {/* static instantly, no waiting on the network */}
      <Suspense fallback={<InventorySkeleton />}>
        {/* streamed in once the dynamic data resolves */}
        <LiveInventory productId={params.id} />
      </Suspense>
    </ProductShell>
  );
}

Механизмом служит граница Suspense. Всё, что находится снаружи неё, предварительно обрабатывается во время сборки и сразу же предоставляется; всё, что находится внутри неё, вычисляется и загружается по запросу. Вот вся суть: один файл, один маршрут, сосуществование двух стратегий отрисовки.

Такой подход приносит несколько конкретных преимуществ. Время до первого байта сокращается, поскольку статическая оболочка загружается непосредственно с сервера, а не вычисляется для каждого запроса. Также отпадает необходимость в состоянии загрузки всей страницы — посетители сразу видят значимые статические части страницы, а менее объемные динамические элементы добавляются по мере их готовности. Кроме того, уменьшается психологическая нагрузка по сравнению с использованием отдельных статических и серверно генерируемых страниц, поскольку работается с одним маршрутом и одним файлом, который просто сочетает разные стратегии. Команда Next.js описывает эту цель как возможность получить преимущества как статической, так и динамической обработки без обычных архитектурных компромиссов.

Несколько практических рекомендаций помогают сделать PPR эффективным в производственных условиях. Сохраняйте границы Suspense узкими только вокруг тех частей, которые действительно динамичны — включение слишком большого объема контента сводит на нет весь смысл предварительной обработки. Сохраняйте легкость фоновых структур, поскольку эти фоновые элементы сами являются частью статически отрендеренной оболочки. Если вы используете TypeScript, определите четкие контракты свойств между статической оболочкой и компонентами, загружаемыми в реальном времени, чтобы оба элемента оставались синхронизированными по мере их развития. А при тестировании ограничьте скорость подключения, например, до уровня медленного 3G, с помощью инструментов разработчика браузера — преимущества PPR становятся гораздо более заметными в реалистичных сетевых условиях, чем при быстром локальном подключении.

Если ваша команда использует Next.js 14 или новее, стоит попробовать технологию PPR для одного маршрута перед её внедрением во всё приложение. Это не маркетинговый термин — это стратегия отрисовки, которая наконец соответствует реальному поведению страниц: они частично статические, частично динамические одновременно.

Применение той же идеи к клиентской отрисовке

Частичная предварительная отрисовка решает проблему разделения на статическое и динамическое на уровне сервера и сети. Одновременная отрисовка, введённая в React 18 и доработанная в React 19, решает аналогичную проблему внутри браузера: вместо выбора между «отрисовать всё сейчас» и «пока ничего не отрисовывать», React теперь может сразу отрисовать некоторые элементы, а остальные — отложить.

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

Механизм параллельной отрисовки меняет эту ситуацию. Теперь React может приостановить процесс отрисовки на полпути, отдавать приоритет срочным обновлениям, таким как ввод текста или нажатия кнопок, перед менее срочными, например фильтрацией длинного списка, а также отклонять текущую работу, если появляется более свежее обновление, делающее предыдущую работу ненужной. Важно отметить, что это не новый API, который нужно изучать с нуля — это просто другая модель планирования, действующая в фоновом режиме под уже существующими хуками.

Хуки, обеспечивающие параллельное планирование

useTransition позволяет отмечать обновления состояния как ненужные срочно, чтобы React мог сохранять отзывчивость интерфейса в то время, как это обновление обрабатывается на фоне.

function ProductSearch() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  function handleChange(e) {
    const value = e.target.value;
    setQuery(value); // urgent — keep input snappy

    startTransition(() => {
      // non-urgent — can be interrupted
      setResults(filterProducts(value));
    });
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <span className="text-gray-400">Updating…</span>}
      <ResultsList items={results} />
    </>
  );
}

Благодаря этой схеме ввод текста в поле поиска остается плавным даже тогда, когда на фоне происходит фильтрация тысяч элементов.

Suspense выполняет аналогичную функцию стриминга на клиенте, как и PPR на сервере: вместо того чтобы блокировать всю страницу во время загрузки данных, он позволяет отдельным частям интерфейса обрабатываться независимо. В сочетании с Next.js App Router это также позволяет использовать серверные компоненты, которые постепенно добавляются на страницу вместо того, чтобы задерживать полную загрузку.

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

Для команд, работающих с React, Next.js, TypeScript, Redux и Tailwind CSS, этая модель планирования имеет значение не только на уровне отдельных хуков — новые асинхронные паттерны в Redux Toolkit и способ работы Next.js Server Actions внутри системы опираются на ту же основу конкурентного планирования, которую теперь предоставляет React. Конкурентность — это не то, что можно включить напрямую; это функционал, который React применяет автоматически, как только компоненты структурированы таким образом, что позволяет прерывать и возобновлять их отрисовку.

Что стоит запомнить

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

Связанные материалы

  • Навык AGENTS.md от Vercel обучает ИИ-ассистентов 70 лучшим практикам React — практический обзор навыка agent react-best-practices от Vercel, демонстрирующий, как он встрояет 70 проверенных в реальных проектах правил React непосредственно в код, сгенерированный ИИ.