Обзор React 19

React 19 — это большой шаг вперед для экосистемы React, наполненный новыми функциями и улучшениями, которые сделают процесс разработки лучше, а наши приложения — мощнее. Давайте рассмотрим новые хуки и API, компоненты React Server и многое другое.

React 19, релиз которого состоялся 5 декабря 2024 года , дебютирует через два года после выпуска React 18, что является важной вехой в его развитии. Эта новая версия наполнена многочисленными обновлениями, такими как новые хуки и API, компоненты React Server, удаление некоторых устаревших API React и многое другое. В сегодняшней статье мы обсудим некоторые из этих новых функций и то, как они могут улучшить ваш опыт разработки React.

Новые хуки и API

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

Form Actions и useActionState Хук

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

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

import React, { useState, useCallback } from "react";

const submitForm = async () => {
  /* form submit */
};

export function Component() {
  // create form state
  const [formState, setFormState] = useState(null);
  const [isPending, setIsPending] = useState(false);

  // handle form submission
  const formAction = useCallback(async (event) => {
    event.preventDefault();
    setIsPending(true);
    try {
      const result = await submitForm();
      setFormState(result);
    } catch (error) {
      setFormState({ message: "Failed to complete action" });
    }
    setIsPending(false);
  }, []);

  // display form template
  return <form onSubmit={formAction}>{/* Form Template */}</form>;
}

В приведенном выше примере компонент обрабатывает состояние формы и обрабатывает отправки формы асинхронно. Когда форма отправляется, функция submitForm() отправляет данные формы на сервер и получает ответ. Затем компонент обновляет свое состояние, чтобы предоставить пользователю обратную связь о процессе отправки.

В React 18 концепция перехода пользовательского интерфейса из одного представления в другое в несрочной манере называлась transitions . React 19 расширяет эту функциональность, поддерживая использование асинхронных функций в transitions.Хук useTransition теперь можно использовать для управления отображением индикаторов загрузки или заполнителей во время асинхронной выборки данных.

В React 19 концепция переходов продвинулась на шаг дальше, поскольку функции, использующие асинхронные переходы, теперь называются Actions. Теперь существует несколько специализированных хуков для управления Actions, подобных тем, что мы видели выше, и первым, на что мы обратим внимание, будет хук useActionState .

Хук useActionState() принимает три параметра:

  1. Функция «action», которая вызывается при запуске действия формы.
  2. Объект начального состояния, устанавливающий начальное состояние формы перед любым взаимодействием с пользователем.
  3. [Необязательно] Постоянная ссылка, указывающая на уникальный URL-адрес страницы, измененный этой формой.

Он возвращает три значения в кортеже:

  1. Текущее состояние формы.
  2. Функция для запуска действия формы.
  3. Логическое значение, указывающее, находится ли действие в состоянии ожидания.
import { useActionState } from "react";

export function Component() {
  const [state, dispatch, isPending] = useActionState(
    action,
    initialState,
    permalink
  );

  // ...
}

Функция action, которая является первым аргументом, переданным хуку useActionState, вызывается при отправке формы. Она возвращает состояние формы, в которое мы ожидаем перейти, независимо от того, была ли отправка успешной или возникли ошибки. Эта функция получает два параметра: текущее состояние формы и данные формы на момент срабатывания действия.

Вот пример создания action() функции, которая вызывает гипотетическую submitForm() функцию, которая затем запускает вызов API для отправки данных формы на сервер. Если действие выполнено успешно, оно возвращает объект состояния формы, представляющий обновленное состояние формы. Если действие не выполнено, оно возвращает объект состояния формы, отражающий состояние ошибки, потенциально включая сообщения об ошибках или индикаторы, которые помогут пользователю исправить проблему.

import { useActionState } from "react";

const submitForm = async (formData) => {
  /* form submit fn that calls API */
};

const action = async (currentState, formData) => {
  try {
    const result = await submitForm(formData);
    return { message: result };
  } catch {
    return { message: "Failed to complete action" };
  }
};

export function Component() {
  const [state, dispatch, isPending] = useActionState(action, null);

  // ...
}

После useActionState() настройки хука мы теперь можем использовать состояние формы, dispatch()функцию и isPending значения в нашем шаблоне формы.

С React 19 <form> элементы теперь имеют action prop, который может получать функцию действия, которая будет запущена при отправке формы. Вот куда мы передадим dispatch функцию из нашего useActionState()хука.

import { useActionState } from "react";

const submitForm = async (formData) => {
  /* form submit fn that calls API */
};

const action = async (currentState, formData) => {
  try {
    const result = await submitForm(formData);
    return { message: result };
  } catch {
    return { message: "Failed to complete action" };
  }
};

export function Component() {
  const [state, dispatch, isPending] = useActionState(action, null);

  return <form action={dispatch}>{/* ... */}</form>;
}

Мы можем отобразить форму state в нашем шаблоне и использовать isPending значение, чтобы указать пользователю, когда выполняется асинхронное действие.

import { useActionState } from "react";

const submitForm = async (formData) => {
  /* form submit fn that calls API */
};

const action = async (currentState, formData) => {
  try {
    const result = await submitForm(formData);
    return { message: result };
  } catch {
    return { message: "Failed to complete action" };
  }
};

export function Component() {
  const [state, dispatch, isPending] = useActionState(action, null);

  return (
    <form action={dispatch}>
      <input type="text" name="text" disabled={isPending} />

      <button type="submit" disabled={isPending}>
        Add Todo
      </button>

      {/* 
        display form state message to convey when 
        form submit is successful or fails.
      */}
      {state.message && <h4>{state.message}</h4>}
    </form>
  );
}

Благодаря этим новым изменениям в React больше нет необходимости вручную обрабатывать состояния ожидания, ошибки и последовательные запросы при работе с асинхронными переходами в формах. Вместо этого мы можем получить доступ к этим значениям напрямую через useActionState() хук.

useFormStatus

React 19 представляет новый хук useFormStatus, который позволяет вложенным дочерним компонентам получать доступ к информации о форме, частью которой они являются, аналогично тому, как форма действует как поставщик контекста.

import { useFormStatus } from "react";

export function NestedComponent() {
  /* access form information */
  const { pending, data, method, action } = useFormStatus();

  return (
    /* template */
  );
}

Хотя доступ к информации родительской формы можно получить с помощью Context , в React 19 появился useFormStatus хук для упрощения обработки данных формы во вложенных компонентах.

useOptimistic

Еще одно новое дополнение в React 19 — это хук useOptimistic . Он позволяет выполнять оптимистичные обновления, пока выполняется фоновая операция, например сетевой запрос. Это улучшает пользовательский опыт, предоставляя более быструю обратную связь по взаимодействиям.

Вот пример того, как useOptimistic() хук можно использовать для управления оптимистичными обновлениями для message свойства состояния, переданного в качестве реквизита.

import { useOptimistic } from "react";

export function Component({ message, updateMessage }) {
  // create "optimistic" state property
  const [optimisticMessage, setOptimisticMessage] = useOptimistic(message);

  const submitForm = async (formData) => {
    const newMessage = formData.get("text");

    // before triggering API change, set new value optimistically
    setOptimisticMessage(newMessage);

    // update actual state when API submission resolves
    const updatedName = await submitToAPI(newMessage);
    updateMessage(updatedName);
  };

  return (
    <form action={submitForm}>
      {/* show optimistic value */}
      <p>{optimisticMessage}</p>

      <input type="text" name="text" />
      <button type="submit">Add Message</button>
    </form>
  );
}

В приведенном выше примере компонента useOptimistic хук управляет оптимистичными обновлениями для message состояния, переданного в качестве свойства.

Когда пользователь отправляет форму, нажимая кнопку «Add Message», submitForm() функция срабатывает. Перед выполнением запроса API на обновление сообщения функция setOptimisticMessage() вызывается с новым значением сообщения из данных формы. Это мгновенно обновляет пользовательский интерфейс, чтобы отразить оптимистичное изменение, предоставляя немедленную обратную связь пользователю.

После завершения или сбоя обновления React автоматически вернется к message значению свойства.

Новый use API

React 19 представляет новый API use, предоставляющий гибкий метод для чтения значений из ресурсов, таких как promeses или контекст. Например, для доступа к значениям контекста мы передаем контекст в use(), и он перемещается по дереву компонентов, чтобы найти ближайшего поставщика контекста.

import { use, createContext } from "react";

const Context = createContext({ data: "Data from context" });

// nested component
function NestedChildComponent() {
  const context = use(Context);
  // ...
}

В отличие от useContext() хука, который традиционно используется для чтения контекста, use()функцию можно использовать в условных операторах и циклах наших компонентов!

import { use, createContext } from "react";

const Context = createContext({ data: "Data from context" });

// nested component
function NestedChildComponent({ value }) {
  if (value) {
    const context = use(Context);
  }

  // ...
}

Функция use() также легко интегрируется с границами приостановки и ошибок для чтения промисов.

React Server Components

React Server Components , новая функция в React 19, позволяет создавать компоненты React без сохранения состояния, которые запускаются на сервере . Эти компоненты выполняются заранее и до объединения в среду, отдельную от клиентского приложения или сервера, визуализируемого на стороне сервера.

Поскольку компоненты React Server работают на веб-сервере, они могут получать доступ к слою данных напрямую, без необходимости взаимодействия с API!

import db from "./database";

// React Server Component
async function BlogPost({ postId }) {
  // Load blog post data from database
  const post = await db.posts.get(postId);

  // Load comments for the post from database
  const comments = await db.comments.getByPostId(postId);

  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.content}</p>
      <h3>Comments</h3>
      <ul>
        {comments.map((comment) => (
          <li key={comment.id}>
            <Comment {...comment} />
          </li>
        ))}
      </ul>
    </div>
  );
}

Как это круто! Это устраняет необходимость в предоставлении конечной точки API или использовании дополнительной клиентской логики выборки для загрузки данных в наши компоненты, поскольку вся обработка данных выполняется на сервере.

Помните, что серверные компоненты работают на сервере, а не в браузере, поэтому они не могут использовать традиционные API компонентов React, такие как useState. Чтобы добавить интерактивность в серверный компонент React, нам нужно использовать клиентские компоненты, которые дополняют серверные компоненты для обработки интерактивности.

Продолжая приведенный выше пример записи в блоге, это будет включать в себя визуализацию Comment компонента как клиентского компонента, включающего некоторое состояние и интерактивность.

// React Client Component
"use client";

export function Comment({ id, text }) {
  const [likes, setLikes] = useState(0);

  function handleLike() {
    setLikes(likes + 1);
  }

  return (
    <div>
      <p>{text}</p>
      <button onClick={handleLike}>Like ({likes})</button>
    </div>
  );
}

Обратите внимание "use client" на объявление в верхней части файла компонента в примере выше. Это указывает на то, что компонент является клиентским компонентом при работе с серверными компонентами React. Это означает, что он может управлять состоянием, обрабатывать взаимодействия с пользователем и использовать API, специфичные для браузера. Эта директива информирует фреймворк React и сборщик о необходимости обрабатывать этот компонент иначе, чем серверные компоненты, которые не имеют состояния и работают на сервере.

С другой стороны, React Server Components являются компонентами по умолчанию, поэтому нам не нужно указывать «use server» в верхней части файлов Server Component. Вместо этого «use server» используется только для обозначения функций на стороне сервера, которые могут быть вызваны из Client Components. Эти функции известны как Server Actions .

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

React 19 также представляет ряд других улучшений для дальнейшего улучшения опыта разработчиков и производительности приложений.

Передача Ref в качестве Props

В React 19 мы теперь можем передавать данные ref напрямую в качестве props компонентам функций:

function MyInput({ placeholder, ref }) {
  return <input placeholder={placeholder} ref={ref} />;
}

// passing ref down as prop
<MyInput ref={ref} />;

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

Контекст как Provider

С React 19 мы сможем выполнять рендеринг <Context> напрямую как provider вместо необходимости указывать <Context.Provider>, что упрощает синтаксис:

import { createContext } from "react";

const MyContext = createContext();

function App() {
  return (
    <MyContext value={/* some value */}>
      {/* Your components */}
    </MyContext>
  );
}

Функция очистки для REFs

Теперь мы можем вернуть функцию очистки из обратных вызовов ref в React 19:

<input
  ref={(ref) => {
    // Ref created
    return () => {
      // Ref cleanup
    };
  }}
/>

Так же, как функция очистки ведет себя в useEffect хуке, функция очистки, возвращаемая из обратного вызова ref, будет вызвана при размонтировании компонента.

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

React 19 добавляет встроенную поддержку для рендеринга тегов метаданных документа, таких как <title>, <link> и <meta> внутри компонентов, тогда как в прошлом разработчики обычно полагались на внешние библиотеки, такие как react-helmet или next/head, для управления этими тегами.

function Component({ data }) {
  return (
    <article>
      <title>{post.title}</title>
      <meta name="author" content="Hassan Djirdeh" />
      <link rel="author" href="https://twitter.com/djirdehh/" />
      <meta name="keywords" content={post.keywords} />
    </article>
  );
}

Эти теги автоматически переносятся в <head> раздел документа, обеспечивая совместимость с клиентскими приложениями, потоковой передачей SSR и серверными компонентами.

Различия и улучшенные сообщения об ошибках

Улучшено сообщение об ошибках гидратации react-dom. Вместо регистрации нескольких ошибок в режиме разработки без подробной информации, React 19 теперь регистрирует одно сообщение с разницей несоответствия.

Uncaught Error: Hydration failed because the server rendered HTML didn’t match the client. As a result this tree will be regenerated on the client. This can happen if an SSR-ed Client Component is used:

- A server/client branch if (typeof window !== 'undefined').
- Variable input such as Date.now() or Math.random() which changes each time it’s called.
- Date formatting in a user’s locale which doesn’t match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.

It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.

https://react.dev/link/hydration-mismatch

  <App>
    <span>
+    Client
-    Server

  at throwOnHydrationMismatch
  …

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

Аналогично, в React 19 были внесены улучшения в отчеты об ошибках для обработки перехваченных и неперехваченных ошибок, регистрируя одно сообщение об ошибке со всей соответствующей информацией вместо дублирующих сообщений об ошибках. Кроме того, были добавлены два новых параметра root для обработки различных типов ошибок в дополнение к существующему onRecoverableErrorпараметру root:

  • onCaughtError: вызывается, когда React обнаруживает ошибку в границе ошибки
  • onUncaughtError: вызывается, когда возникает ошибка, не перехваченная границей ошибки

И другие улучшения

React 19 также включает:

  • Поддержка пользовательских элементов: полная поддержка пользовательских элементов, обработка props как атрибутов или свойств соответствующим образом
  • Поддержка асинхронных скриптов: улучшенная поддержка асинхронных скриптов, позволяющая отображать их в любом месте дерева компонентов.
  • Предварительная загрузка ресурсов: новые API для предварительной загрузки ресурсов, оптимизации начальной загрузки страниц и обновлений на стороне клиента.
  • Совместимость со сторонними скриптами и расширениями: улучшенная гидратация для учета сторонних скриптов и расширений браузера.

Заключение

React 19 — это большой шаг вперед для экосистемы React, наполненный новыми функциями и улучшениями, которые улучшают процесс разработки и делают наши приложения более мощными. С новыми хуками и API, компонентами React Server и удалением устаревших API есть много поводов для волнения.

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