Назад
Назад к заметкам
· 4 мин чтения

React 19: что нового и как сразу применять

React 19 предлагает новые хуки и улучшения производительности: useOptimistic, гибкие серверные компоненты, автоматическая partial hydration и selective concurrency.

reactreact-19performancehooksconcurrency

Если ваш список товаров начинает “тормозить” при прокрутке, а useEffect-ы постоянно пере-подписываются, обычные оптимизации уже не спасают. В React 19 появилось несколько функций, позволяющих избавиться от лишних рендеров и ускорить загрузку без кардинального рефакторинга.

Новый хук useOptimistic “прогрессивный” стейт

useOptimistic обновляет UI сразу после действия пользователя, пока сервер не подтвердил изменение. При ошибке состояние откатывается автоматически. В React 18 такой механизм был экспериментальным (unstable_optimistic), а в 19 хук стал стабильным.

import { useOptimistic } from "react";

function LikeButton({ postId }) {
  const [optimisticLikes, setOptimisticLikes] = useOptimistic(
    0,
    (prev, delta) => prev + delta
  );

  const handleLike = async () => {
    // мгновенно увеличиваем UI
    setOptimisticLikes(1);
    try {
      await fetch(`/api/posts/${postId}/like`, { method: "POST" });
    } catch {
      // откат при неудачном запросе
      setOptimisticLikes(-1);
    }
  };

  return <button onClick={handleLike}>👍 {optimisticLikes}</button>;
}

Плюсы:

  • Визуальная отзывчивость без отдельного loading-состояния.
  • Автоматический откат, без необходимости в Redux-слое.
ПлюсКогда использовать
Мгновенный откликИнтерактивные действия (лайки, добавление в корзину)
Минимум кодаПри использовании fetch/axios
Совместимо с SuspenseДа, рендер не блокируется

Больше о мемоизации компонентов читайте в статье про мемоизацию компонентов.

Серверные компоненты 2.0: гибкая граница клиент-/сервер-рэндера

React 19 расширил API серверных компонентов (RSC) и позволил миксовать их с клиентскими без лишних оберток. Теперь можно импортировать полутяжелые UI-блоки напрямую в клиентские компоненты без явного "use client".

// src/components/ProductList.server.tsx
export default function ProductList({ products }) {
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>
          {p.name} ${p.price}
        </li>
      ))}
    </ul>
  );
}

// src/app/page.tsx (клиентский)
import ProductList from "./components/ProductList.server";

export default function Page() {
  const products = use(fetchProducts()); // data-fetcher из React 19
  return (
    <section>
      <h1>Товары</h1>
      <ProductList products={products} />
    </section>
  );
}

Отличие от 18-го: ProductList может получать стриминг-данные и обновляться без повторного рендера родителя. Это упрощает построение больших страниц, где меняется только часть контента.

Автоматическая partial hydration

Раньше hydrateRoot требовал вручную указывать точки гидратации. В React 19 появилась автоматическая частичная гидратация: фреймворк сам определяет, какие участки уже отрендерены на сервере и оставляет их “неактивными”, пока пользователь не взаимодействует.

// index.tsx
import { createRoot } from "react-dom/client";
import App from "./App";

createRoot(document.getElementById("root")).hydrate(<App />);

При первом клике внутри <App> React “включит” соответствующую часть дерева, уменьшая стартовый JavaScript-бандл почти вдвое, особенно в приложениях с большим количеством статических страниц.

Что проверить:

  • Сервер отдает полноценный HTML (без dangerouslySetInnerHTML).
  • Отключите StrictMode в продакшене, иначе будет двойной рендер, усложняющий отладку.

Расширенный Suspense-API для данных

React 19 добавил свойство fallback в use-хук, позволяющее задать запасный UI непосредственно в месте данных, без необходимости оборачивать все дерево в <Suspense>. Подробности в статье про новый хук use в React 19.

function Profile({ userId }) {
  const user = use(fetchUser(userId)); // поддерживает Promise
  return (
    <section>
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
    </section>
  );
}

// Можно опустить <Suspense>, если задать fallback в use
function Page({ id }) {
  return (
    <Suspense fallback={<Spinner />}>
      <Profile userId={id} />
    </Suspense>
  );
}

React 19 автоматически заменит fallback на содержимое Promise, пока оно не разрешилось, упрощая асинхронный код.

Переработанный Concurrent Mode: “только-по-необходимости”

В React 18 Concurrent Mode включался глобально. В 19-м появился selective concurrency режим можно включать только для тяжелых компонентов, оставляя легкие в синхронном режиме. Делается это через новый параметр concurrent в createRoot.

import { createRoot } from "react-dom/client";

// Список товаров рендерится в concurrency
createRoot(document.getElementById("list"), { concurrent: true }).render(
  <ProductList />
);

// Обычные UI-элементы без concurrency
createRoot(document.getElementById("header")).render(<Header />);

Гибкость экономит CPU, особенно на мобильных устройствах, где полное concurrency может приводить к “перепрыгиванию” фреймов.


FAQ / чеклист

  • Не включайте concurrent для всех компонентов теряется контроль над производительностью. Выбирайте тяжелые списки или табличные данные.
  • Держите fallback в use иначе пользователь увидит белый экран, пока Promise не выполнится.
  • Не совмещайте useOptimistic с useEffect, меняющим то же состояние может возникнуть конфликт обновлений.
  • Серверные компоненты должны оставаться “чистыми” их цель минимальный JavaScript.
  • Отслеживайте ошибки автоматической гидратации проверяйте консоль на предупреждения о “mis-match” HTML/JS.

React 19 уже доступен в stable-теге, и новые возможности легко интегрировать в существующий код. Попробуйте добавить useOptimistic там, где пользователь ожидает мгновенного отклика, а остальные улучшения работают “по-умолчанию”.

Готов начать?

Выбери удобный способ связи — напиши напрямую или оставь заявку

Написать в Telegram

Отвечу в течение пары часов