Как стать автором
Обновить
1621.71
Timeweb Cloud
То самое облако

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

Уровень сложностиСредний
Время на прочтение13 мин
Количество просмотров7.8K
Автор оригинала: Michael Novotny



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

Теги:
Хабы:
+30
Комментарии8

Публикации

Информация

Сайт
timeweb.cloud
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия
Представитель
Timeweb Cloud