Что нового в React 19

от автора

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-канале


ссылка на оригинал статьи https://habr.com/ru/articles/843016/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *