
React 19 на подходе. Команда React анонсировала предрелизную версию React 19 в апреле. Это крупное обновление принесет с собой ряд улучшений и новых паттернов, нацеленных на повышение производительности, удобство использования и опыта разработки.
Многие из этих возможностей были представлены в экспериментальном режиме в React 18, но в React 19 они станут стабильными. Давайте подготовимся к этому обновлению.
❯ Серверные компоненты
Серверные компоненты — одно из крупнейших изменений в React с момента его первого релиза 10 лет назад. Они служат фундаментом для новых функций React 19, улучшая:
- Время первоначальной загрузки страницы. Рендеринг компонентов на сервере сокращает объем отправляемого клиенту JavaScript-кода, что ускоряет начальную загрузку страницы. Кроме того, это позволяет получать данные на сервере еще до отправки страницы клиенту.
- Переносимость кода. Серверные компоненты позволяют разработчикам создавать компоненты, которые могут работать как на сервере, так и на клиенте. Это помогает избежать дублирования кода, упрощает поддержку и облегчает совместное использование логики по всему приложению.
- SEO. Серверный рендеринг компонентов позволяет поисковым системам и языковым моделям более эффективно обрабатывать и индексировать содержимое страниц.
В этой статье мы не будем углубляться в серверные компоненты и стратегии рендеринга. Однако, чтобы в полной мере оценить роль серверных компонентов, необходимо кратко рассмотреть, как развивался процесс рендеринга в React.
Изначально React использовал клиентский рендеринг (Client-Side Rendering — CSR), который отправлял пользователю минимальный HTML.
<!DOCTYPE html>
<html>
<body>
<div id="root"></div>
<script src="/static/js/bundle.js"></script>
</body>
</html>
Прикрепленный скрипт включает в себя весь код приложения — React, сторонние зависимости/библиотеки и собственный код. По мере роста приложения размер этого "пакета" кода становился все больше, что замедляло начальную загрузку. Когда пользователь переходил на страницу, сначала он видел пустой экран, пока этот JS-файл загружался, разбирался браузером, и пока React загружал DOM-элементы в пустой div
.
Чтобы скрыть этот некрасивый "пустой" период, разработчики стали применять "скелетоны" — временные визуальные заглушки, которые отображались до загрузки и рендеринга реальных данных.

React усовершенствовался благодаря серверному рендерингу (Server-Side Rendering — SSR). Этот подход предполагает, что первоначальный рендеринг происходит на сервере, а не на клиенте, что позволяет отправлять пользователю HTML-код с готовым начальным интерфейсом, ускоряя его отображение. Однако даже в этом случае для показа реального содержимого страницы требуется дополнительная загрузка данных с сервера.

Фреймворки React расширили свои возможности, чтобы улучшить пользовательский опыт, внедрив такие концепции, как генерация статического контента (Static-Site Generation — SSG) и его инкрементная регенерация (Incremental Static Regeneration — ISR).
SSG предполагает предварительную сборку и кэширование динамических данных во время генерации сайта. ISR дает возможность обновлять этот кэш по мере необходимости, без полной перезагрузки страницы.
И, наконец, появились серверные компоненты React (React Server Components — RSC). Впервые в самом React появилась возможность загружать данные до рендеринга и отображения пользовательского интерфейса.
export default async function Page() {
const res = await fetch('https://api.example.com/products')
const products = res.json()
return (
<>
<h1>Products</h1>
{products.map((product) => (
<div key={product.id}>
<h2>{product.title}</h2>
<p>{product.description}</p>
</div>
))}
</>
)
}
HTML, предоставляемый пользователю, полностью заполнен реальным содержимым при первом рендеринге. Нет необходимости в дополнительной загрузке данных или повторном рендеринге.

Появление серверных компонентов — это большой шаг вперед в повышении скорости и производительности приложений. Подробнее ознакомиться с RSC можно здесь.
Визуальные иллюстрации рендеринга вдохновлены работами Josh W. Comeau.
❯ Новые директивы
Директивы не являются особенностью React 19, но тесно связаны с этой версией фреймворка. Из-за введения RSC, у сборщиков появилась необходимость различать, где именно выполняется код компонентов и функций — на клиенте или на сервере. Для этого введены две новые директивы:
-
'use client'
— отмечает код, который работает только на клиенте. Поскольку серверные компоненты являются стандартными,'use client'
добавляется в клиентские компоненты при использовании хуков для интерактивности и состояния. -
'use server'
— отмечает серверные функции, которые могут вызываться из клиентского кода. Не нужно добавлять'use server
' к серверным компонентам, только к серверным операциям (подробнее об этом ниже). Если требуется, чтобы определенный код выполнялся только на сервере,можно использовать пакет server-only
.
Подробнее о директивах можно узнать здесь.
❯ Операции
React 19 вводит концепцию операций (actions). Эти функции заменяют обработчики событий и интегрируются с переходами (transitions) и параллельными (concurrent) возможностями React.
Операции могут использоваться как на клиенте, так и на сервере. Например, можно создать операцию, которая будет обрабатывать отправку формы вместо традиционного обработчика onSubmit
.
Вместо необходимости обрабатывать событие, в операцию напрямую передается объект FormData
:
import { useState } from 'react'
export default function TodoApp() {
const [items, setItems] = useState([{ text: 'My first todo' }])
async function formAction(formData) {
const newItem = formData.get('item')
// Отправляет POST-запрос на сервер для сохранения нового элемента
setItems((items) => [...items, { text: newItem }])
}
return (
<>
<h1>Todo List</h1>
<form action={formAction}>
<input type='text' name='item' placeholder='Add todo...' />
<button type='submit'>Add</button>
</form>
<ul>
{items.map((item, index) => (
<li key={index}>{item.text}</li>
))}
</ul>
</>
)
}
Серверные операции
Серверные операции (server actions) позволяют клиентским компонентам вызывать асинхронные функции, выполняемые на сервере. Это дает дополнительные преимущества, такие как возможность чтения файловой системы или прямое обращение к базе данных, устраняя необходимость создания специальных конечных точек API для пользовательского интерфейса.
Операции определяются с помощью директивы 'use server'
и интегрируются с клиентскими компонентами.
Чтобы использовать серверную операцию в клиентском компоненте, необходимо создать новый файл и импортировать его:
'use server'
export async function create() {
// Сохраняем данные в БД
}
'use client'
import { create } from './actions'
export default function TodoList() {
return (
<>
<h1>Todo List</h1>
<form action={create}>
<input type='text' name='item' placeholder='Add todo...' />
<button type='submit'>Add</button>
</form>
</>
)
}
Подробнее о серверных операциях можно узнать здесь.
❯ Новые хуки
Для того, чтобы дополнить концепцию операций, в React 19 представлены три новых хука, которые упрощают работу с состоянием, статусом и визуальной обратной связью. Эти хуки особенно полезны при работе с формами, но могут быть использованы и для других элементов, например, для кнопок.
useActionState
Хук useActionState
упрощает работу с состоянием форм и их отправкой. Используя операции, он обрабатывает данные, вводимые в форму, валидацию и ошибки, избавляя от необходимости написания клиентской логики управления состоянием. Этот хук также предоставляет состояние pending
, которое можно использовать для отображения индикатора загрузки во время выполнения операции:
'use client'
import { useActionState } from 'react'
import { createUser } from './actions'
const initialState = {
message: '',
}
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor='email'>Email</label>
<input type='text' id='email' name='email' required />
{/* ... */}
{state?.message && <p aria-live='polite'>{state.message}</p>}
<button aria-disabled={pending} type='submit'>
{pending ? 'Submitting...' : 'Sign up'}
</button>
</form>
)
}
Подробнее о useActionState
можно узнать здесь.
useFormStatus
Хук useFormStatus
отслеживает статус последней отправки формы. Он должен вызываться из компонента, который находится в составе формы:
import { useFormStatus } from 'react-dom'
import action from './actions'
function Submit() {
const status = useFormStatus()
return <button disabled={status.pending}>Submit</button>
}
export default function App() {
return (
<form action={action}>
<Submit />
</form>
)
}
В то время как useActionState
имеет встроенное состояние pending
, хук useFormStatus
может быть особенно полезен в следующих ситуациях:
- когда нет необходимости в управлении состоянием всей формы
- при создании повторно используемых компонентов форм
- когда на одной странице есть несколько форм,
useFormStatus
возвращает информацию о статусе только родительской формы
Подробнее о useFormStatus
можно узнать здесь.
useOptimistic
Этот хук позволяет оптимистично обновлять пользовательский интерфейс до завершения серверной операции, вместо ожидания ответа. Когда асинхронная операция завершается, пользовательский интерфейс обновляется с окончательным состоянием, полученным с сервера.
Следующий пример демонстрирует, как можно добавить новое сообщение в тред, не дожидаясь завершения отправки сообщения на сервер:
'use client'
import { useOptimistic } from 'react'
import { send } from './actions'
export function Thread({ messages }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [...state, { message: newMessage }],
)
const formAction = async (formData) => {
const message = formData.get('message')
addOptimisticMessage(message)
await send(message)
}
return (
<div>
{optimisticMessages.map((m, i) => (
<div key={i}>{m.message}</div>
))}
<form action={formAction}>
<input type='text' name='message' />
<button type='submit'>Send</button>
</form>
</div>
)
}
Подробнее о useOptimistic
можно узнать здесь.
❯ Новое API: use
Функция use
предназначена для работы с промисами и контекстом во время рендеринга. В отличие от других хуков React, use
может вызываться внутри циклов, условных операторов и ранних возвратов. Обработка ошибок и отображение загрузки выполняются ближайшим компонентом Suspense
.
Следующий пример демонстрирует, как отображать сообщение о загрузке во время выполнения промиса для получения данных корзины товаров:
import { use } from 'react'
function Cart({ cartPromise }) {
// Функция `use` приостанавливает выполнение компонента, пока промис не будет разрешен
const cart = use(cartPromise)
return cart.map((item) => <p key={item.id}>{item.title}</p>)
}
function Page({ cartPromise }) {
return (
// Во время приостановки выполнения `Cart`, отображается этот `Suspense`
<Suspense fallback={<div>Loading...</div>}>
<Cart cartPromise={cartPromise} />
</Suspense>
)
}
Это позволяет объединить компоненты в группу, чтобы они рендерились только тогда, когда доступны данные для всех входящих в нее компонентов.
Подробнее о use
можно узнать здесь.
❯ Предварительная загрузка ресурсов
В React 19 добавлено несколько новых API-интерфейсов для повышения производительности приложений и улучшения пользовательского опыта. Эти API позволяют выполнять обычную и предварительную загрузку ресурсов (скрипты, таблицы стилей и шрифты):
-
prefetchDNS
осуществляет предварительную загрузку IP-адреса доменного имени DNS, с которым ожидается соединение -
preconnect
устанавливает соединение с сервером, от которого ожидается запрос ресурсов, даже если точные ресурсы неизвестны -
preload
выполняет предварительную загрузку таблицы стилей, шрифта, изображения или внешнего скрипта, которые планируется использовать -
preloadModule
осуществляет предварительную загрузку модуля ESM, который планируется использовать -
preinit
выполняет предварительную загрузку и оценку (evaluation) внешнего скрипта или предварительную загрузку и вставку таблицы стилей -
preinitModule
выполняет предварительную загрузку и оценку модуля ESM
Использование данных API-интерфейсов в React-коде приведет к соответствующей разметке HTML, где ссылки и скрипты будут упорядочены по приоритету загрузки, а не по порядку их использования:
// React code
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom'
function MyComponent() {
preinit('https://.../path/to/some/script.js', { as: 'script' })
preload('https://.../path/to/some/font.woff', { as: 'font' })
preload('https://.../path/to/some/stylesheet.css', { as: 'style' })
prefetchDNS('https://...')
preconnect('https://...')
}
<!-- Итоговый HTML -->
<html>
<head>
<link rel="prefetch-dns" href="https://..." />
<link rel="preconnect" href="https://..." />
<link rel="preload" as="font" href="https://.../path/to/some/font.woff" />
<link
rel="preload"
as="style"
href="https://.../path/to/some/stylesheet.css"
/>
<script async="" src="https://.../path/to/some/script.js"></script>
</head>
<body>
<!-- ... -->
</body>
</html>
React-фреймворки часто берут на себя обработку загрузки ресурсов, в таких случаях нет необходимости самостоятельно вызывать эти API-интерфейсы.
Подробнее об API-интерфейсах предварительной загрузки ресурсов можно узнать здесь.
❯ Другие улучшения
ref
как проп
Больше нет необходимости использовать forwardRef
. React предоставит codemod для облегчения перехода на новый метод.
function CustomInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />
}
// <CustomInput ref={ref} />
Функция очистки ref
Внутри ref
можно возвращать функцию для очистки, которая вызывается при размонтировании компонента:
<input
ref={(ref) => {
// Создание ref
return () => {
// Очистка ref
}
}}
/>
Context
как провайдер
Больше нет необходимости в использовании <Context.Provider>
. Вместо этого можно использовать непосредственно <Context>
. React предоставит codemod для конвертации существующих провайдеров.
const ThemeContext = createContext('')
function App({ children }) {
return <ThemeContext value='dark'>{children}</ThemeContext>
}
Начальное значение для useDeferredValue
В хук useDeferredValue
была добавлена настройка initialValue
. При ее указании useDeferredValue()
будет использовать это значение для первоначального рендеринга, а затем запланирует повторный рендеринг в фоновом режиме, возвращая deferredValue
:
function Search({ deferredValue }) {
// При первоначальном рендеринге `value` равняется пустой строке ''.
// Затем планируется повторный рендеринг для обновления `value` значением `deferredValue`
const value = useDeferredValue(deferredValue, '')
return <Results value={value} />
}
Поддержка метаданных документа
В React 19 появилась встроенная возможность динамически формировать и отображать теги title
, link
и meta
, даже если они определены во вложенных компонентах. Больше нет необходимости в использовании сторонних решений для управления этими тегами.
function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<title>{post.title}</title>
<meta name='author' content='Jane Doe' />
<link rel='author' href='https://x.com/janedoe' />
<meta name='keywords' content={post.keywords} />
<p>...</p>
</article>
)
}
Поддержка таблиц стилей
В React 19 появилась возможность управлять порядком загрузки таблиц стилей с учетом их приоритета (precedence
). Такой подход позволяет более органично размещать таблицы стилей вместе с их компонентами, при этом React будет подгружать их только по мере необходимости.
Вот несколько ключевых моментов:
- если один и тот же компонент рендерится в нескольких местах приложения, React выполнит дедупликацию и включит соответствующую таблицу стилей в документ только один раз
- при серверном рендеринге React добавит таблицу стилей в секцию
<head>
. Это гарантирует, что браузер не начнет отображение контента, пока таблица стилей не будет полностью загружена - если таблица стилей обнаружена уже после начала потокового рендеринга, React вставит эту таблицу стилей в
<head>
на клиентской стороне до отображения контента, зависящего от этих стилей, с помощьюSuspense
- во время клиентского рендеринга React будет ждать загрузки вновь добавленных таблиц стилей, прежде чем фиксировать (commit) результат рендеринга
function ComponentOne() {
return (
<Suspense fallback='loading...'>
<link rel='stylesheet' href='one' precedence='default' />
<link rel='stylesheet' href='two' precedence='high' />
<article>...</article>
</Suspense>
)
}
function ComponentTwo() {
return (
<div>
<p>...</p>
{/* "three" будет вставлена между "one" и "two" */}
<link rel='stylesheet' href='three' precedence='default' />
</div>
)
}
Поддержка асинхронных скриптов
В React 19 появилась возможность рендерить асинхронные скрипты в любом компоненте. Это упрощает размещение скриптов рядом с соответствующими компонентами. React будет загружать их только при необходимости.
Вот несколько ключевых моментов:
- если один и тот же компонент рендерится в нескольких местах приложения, React выполнит дедупликацию и включит скрипт в документ только один раз
- при серверном рендеринге асинхронные скрипты будут добавлены в секцию
<head>
и будут иметь более низкий приоритет по сравнению с более важными ресурсами, блокирующими отображение, такими как таблицы стилей, шрифты и предзагрузка изображений
function Component() {
return (
<div>
<script async={true} src='...' />
</div>
)
}
function App() {
return (
<html>
<body>
<Component>...</Component> // Скрипт не будет продублирован в DOM
</body>
</html>
)
}
Поддержка пользовательских элементов
Пользовательские элементы (custom elements) позволяют разработчикам определять собственные HTML-элементы согласно спецификации Web Components. В предыдущих версиях React использовать пользовательские элементы было сложно, потому что React обрабатывал неизвестные пропы как атрибуты, а не как свойства.
Теперь в React 19 реализована полноценная поддержка пользовательских элементов, и они успешно проходят все тесты на платформе Custom Elements Everywhere.
Улучшенная обработка ошибок
Удаление дублирующихся сообщений об ошибках повышает эффективность их обработки.

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

В React 19 ошибка отображается только один раз.
В React 19 ошибки гидратации отображаются более оптимально — вместо нескольких ошибок, отображается только одно сообщение об ошибке несоответствия (mismatch error). При этом сообщения об ошибках содержат информацию о том, как можно их исправить.

Пример сообщения об ошибке гидратации в React 18.

Пример улучшенного сообщения об ошибке гидратации в React 19.
В React 19 также улучшена обработка ошибок гидратации при использовании сторонних скриптов и браузерных расширений. Ранее, элементы, добавленные сторонними скриптами или браузерными расширениями, вызывали ошибку несоответствия. В React 19 такие неожиданные (unexpected) теги в head
и body
будут просто игнорироваться.
Кроме того, React 19 вводит две новые настройки для корневого элемента приложения, помимо существующей onRecoverableError
. Эти настройки должны помочь разобраться с причинами ошибок:
-
onCaughtError
срабатывает, когда React перехватывает ошибку в предохранителе -
onUncaughtError
срабатывает, когда ошибка не перехватывается предохранителем -
onRecoverableError
срабатывает, когда ошибка возникает, но автоматически устраняется
Релиз React 19 стал важным этапом развития этого фреймворка. Он привнес множество новых, мощных возможностей. Эти усовершенствования повысили производительность React и сделали его использование более комфортным как для разработчиков, так и для пользователей.
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩
Комментарии (9)
metalidea
25.09.2024 09:51Нельзя было без use client понять что в коде есть useEffect и контекст?(
Потом уберут и придется по всему коду ходить и удалять use client, как в свое время удаляли import React
aio350 Автор
25.09.2024 09:51Тут есть о чем подумать. Опыт показывает, что клиентских компонентов в приложении гораздо больше, чем серверных, поэтому приходится постоянно писать
use client
. С другой стороны, серверные компоненты все-таки являются первичными с точки зрения рендеринга в Next.js. На подходе Turbopack и React Compiler, возможно, кто-то из них будет автоматически определять клиентские компоненты. А для удаленияuse client
, скорее всего, выпустят codemod.
Psychosynthesis
25.09.2024 09:51Я в итоге не понял, SSR без текста будет или нет? Всё это безумие с серверным рендерингом выглядит крайне погано.
gsaw
Если у меня чисто клиентское приложение, мне придется везде прописать "use client" при миграции на 19?
aio350 Автор
Нет, это перевод статьи из блога Vercel, подразумевается использование Next.js. В SPA у вас весь код работает на клиенте, нет необходимости специально помечать его с помощью
'use client'
.Alesh
Кстати Next.js упомянуть стоило, думаю большинство весь этот server side из статьи будут именно в рамках Next.js приложения использовать.
aio350 Автор
Цитата из официальной документации React:
"React - это библиотека. Она позволяет использовать компоненты, но не предписывает, как выполнять маршрутизацию или получение данных. Для разработки приложения с помощью React рекомендуется использовать клиент-серверный фреймворк React, такой как Next.js или Remix".
Alesh
Это как-то противоречит моему комментарию?)
aio350 Автор
Нет, я его просто дополнил)