[Перевод] Что нового в React 19

jchwst6a3nwlxlscnmuw5tdhmry.png

React 19 на подходе. Команда React анонсировала предрелизную версию React 19 в апреле. Это крупное обновление принесет с собой ряд улучшений и новых паттернов, нацеленных на повышение производительности, удобство использования и опыта разработки.

Многие из этих возможностей были представлены в экспериментальном режиме в React 18, но в React 19 они станут стабильными. Давайте подготовимся к этому обновлению.


❯ Серверные компоненты

Серверные компоненты — одно из крупнейших изменений в React с момента его первого релиза 10 лет назад. Они служат фундаментом для новых функций React 19, улучшая:


  • Время первоначальной загрузки страницы. Рендеринг компонентов на сервере сокращает объем отправляемого клиенту JavaScript-кода, что ускоряет начальную загрузку страницы. Кроме того, это позволяет плучать данные на сервере еще до отправки страницы клиенту.
  • Переносимость кода. Серверные компоненты позволяют разработчикам создавать компоненты, которые могут работать как на сервере, так и на клиенте. Это помогает избежать дублирования кода, упрощает поддержку и облегчает совместное использование логики по всему приложению.
  • SEO. Серверный рендеринг компонентов позволяет поисковым системам и языковым моделям более эффективно обрабатывать и индексировать содержимое страниц.

В этой статье мы не будем углубляться в серверные компоненты и стратегии рендеринга. Однако, чтобы в полной мере оценить роль серверных компонентов, необходимо кратко рассмотреть, как развивался процесс рендеринга в React.

Изначально React использовал клиентский рендеринг (Client-Side Rendering — CSR), который отправлял пользователю минимальный HTML.



  
    

Прикрепленный скрипт включает в себя весь код приложения — React, сторонние зависимости/библиотеки и собственный код. По мере роста приложения размер этого «пакета» кода становился все больше, что замедляло начальную загрузку. Когда пользователь переходил на страницу, сначала он видел пустой экран, пока этот JS-файл загружался, разбирался браузером, и пока React загружал DOM-элементы в пустой div.

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


effpocn_jas3pmtxbcthcdzrlim.png

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


gxtuho0dyyqmkoookmig25onde0.png

Фреймворки 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 (
    <>
      

Products

{products.map((product) => (

{product.title}

{product.description}

))} ) }

HTML, предоставляемый пользователю, полностью заполнен реальным содержимым при первом рендеринге. Нет необходимости в дополнительной загрузке данных или повторном рендеринге.


n4w0oh-1rtybdb-jnhiieml_jkm.png

Появление серверных компонентов — это большой шаг вперед в повышении скорости и производительности приложений. Подробнее ознакомиться с 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 (
    <>
      

Todo List

    {items.map((item, index) => (
  • {item.text}
  • ))}
) }


Серверные операции

Серверные операции (server actions) позволяют клиентским компонентам вызывать асинхронные функции, выполняемые на сервере. Это дает дополнительные преимущества, такие как возможность чтения файловой системы или прямое обращение к базе данных, устраняя необходимость создания специальных конечных точек API для пользовательского интерфейса.

Операции определяются с помощью директивы 'use server' и интегрируются с клиентскими компонентами.

Чтобы использовать серверную операцию в клиентском компоненте, необходимо создать новый файл и импортировать его:

'use server'

export async function create() {
  // Сохраняем данные в БД
}
'use client'

import { create } from './actions'

export default function TodoList() {
  return (
    <>
      

Todo List

) }

Подробнее о серверных операциях можно узнать здесь.


❯ Новые хуки

Для того, чтобы дополнить концепцию операций, в 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 (
    
{/* ... */} {state?.message &&

{state.message}

}
) }

Подробнее о useActionState можно узнать здесь.


useFormStatus

Хук useFormStatus отслеживает статус последней отправки формы. Он должен вызываться из компонента, который находится в составе формы:

import { useFormStatus } from 'react-dom'
import action from './actions'

function Submit() {
  const status = useFormStatus()
  return 
}

export default function App() {
  return (
    
) }

В то время как 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 (
    
{optimisticMessages.map((m, i) => (
{m.message}
))}
) }

Подробнее о useOptimistic можно узнать здесь.


❯ Новое API: use

Функция use предназначена для работы с промисами и контекстом во время рендеринга. В отличие от других хуков React, use может вызываться внутри циклов, условных операторов и ранних возвратов. Обработка ошибок и отображение загрузки выполняются ближайшим компонентом Suspense.

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

import { use } from 'react'

function Cart({ cartPromise }) {
  // Функция `use` приостанавливает выполнение компонента, пока промис не будет разрешен
  const cart = use(cartPromise)

  return cart.map((item) => 

{item.title}

) } function Page({ cartPromise }) { return ( // Во время приостановки выполнения `Cart`, отображается этот `Suspense` Loading...
}> ) }

Это позволяет объединить компоненты в группу, чтобы они рендерились только тогда, когда доступны данные для всех входящих в нее компонентов.

Подробнее о 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://...')
}


  
    
    
    
    
    
  
  
    
  

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

Подробнее об API-интерфейсах предварительной загрузки ресурсов можно узнать здесь.


❯ Другие улучшения


ref как проп

Больше нет необходимости использовать forwardRef. React предоставит codemod для облегчения перехода на новый метод.

function CustomInput({ placeholder, ref }) {
  return 
}

// 


Функция очистки ref

Внутри ref можно возвращать функцию для очистки, которая вызывается при размонтировании компонента:

 {
    // Создание ref
    return () => {
      // Очистка ref
    }
  }}
/>


Context как провайдер

Больше нет необходимости в использовании . Вместо этого можно использовать непосредственно . React предоставит codemod для конвертации существующих провайдеров.

const ThemeContext = createContext('')

function App({ children }) {
  return {children}
}


Начальное значение для useDeferredValue

В хук useDeferredValue была добавлена настройка initialValue. При ее указании useDeferredValue() будет использовать это значение для первоначального рендеринга, а затем запланирует повторный рендеринг в фоновом режиме, возвращая deferredValue:

function Search({ deferredValue }) {
  // При первоначальном рендеринге `value` равняется пустой строке ''.
  // Затем планируется повторный рендеринг для обновления `value` значением `deferredValue`
  const value = useDeferredValue(deferredValue, '')

  return 
}


Поддержка метаданных документа

В React 19 появилась встроенная возможность динамически формировать и отображать теги title, link и meta, даже если они определены во вложенных компонентах. Больше нет необходимости в использовании сторонних решений для управления этими тегами.

function BlogPost({ post }) {
  return (
    

{post.title}

{post.title}

...

) }


Поддержка таблиц стилей

В React 19 появилась возможность управлять порядком загрузки таблиц стилей с учетом их приоритета (precedence). Такой подход позволяет более органично размещать таблицы стилей вместе с их компонентами, при этом React будет подгружать их только по мере необходимости.

Вот несколько ключевых моментов:


  • если один и тот же компонент рендерится в нескольких местах приложения, React выполнит дедупликацию и включит соответствующую таблицу стилей в документ только один раз
  • при серверном рендеринге React добавит таблицу стилей в секцию . Это гарантирует, что браузер не начнет отображение контента, пока таблица стилей не будет полностью загружена
  • если таблица стилей обнаружена уже после начала потокового рендеринга, React вставит эту таблицу стилей в  на клиентской стороне до отображения контента, зависящего от этих стилей, с помощью Suspense
  • во время клиентского рендеринга React будет ждать загрузки вновь добавленных таблиц стилей, прежде чем фиксировать (commit) результат рендеринга
function ComponentOne() {
  return (
    
      
      
      
...
) } function ComponentTwo() { return (

...

{/* "three" будет вставлена между "one" и "two" */}
) }


Поддержка асинхронных скриптов

В React 19 появилась возможность рендерить асинхронные скрипты в любом компоненте. Это упрощает размещение скриптов рядом с соответствующими компонентами. React будет загружать их только при необходимости.

Вот несколько ключевых моментов:


  • если один и тот же компонент рендерится в нескольких местах приложения, React выполнит дедупликацию и включит скрипт в документ только один раз
  • при серверном рендеринге асинхронные скрипты будут добавлены в секцию и будут иметь более низкий приоритет по сравнению с более важными ресурсами, блокирующими отображение, такими как таблицы стилей, шрифты и предзагрузка изображений
function Component() {
  return (