React 19: что нового и как сразу применять
React 19 предлагает новые хуки и улучшения производительности: useOptimistic, гибкие серверные компоненты, автоматическая partial hydration и selective concurrency.
Если ваш список товаров начинает “тормозить” при прокрутке, а 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
там, где пользователь ожидает мгновенного отклика, а остальные улучшения работают “по-умолчанию”.