Кирилл Мыльников 30 сентября 2024

🚀 Новые возможности React 19: конец эры повторных рендерингов

Новый компилятор React 19 обещает существенное повышение производительности, что станет настоящим прорывом для разработчиков. Но это лишь верхушка айсберга. В этой статье мы не только разберемся, как работает компилятор, но и погрузимся в мир других новейших возможностей React 19.
1
🚀 Новые возможности React 19: конец эры повторных рендерингов

Привет, друзья! Я Кирилл Мыльников, фронтенд-разработчик в компании Usetech. Сегодня хочу поделиться о новейших функциях в React 19. Давайте вместе применим их в практике, разберем новшества и возможности на примерах, чтобы узнать, как они могут улучшить нашу разработку! Готовы к погружению?

Компилятор React

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

Пример

        // Component to measure and log the time taken to render the header
function TimeToRender() {
  // Record the start time when the component begins rendering
  const startTime = performance.now();

  useEffect(() => {
    // Record the end time when the component has rendered
    const endTime = performance.now();
    // Log the time taken to render the component
    console.log(`CustomHeader render time: ${endTime - startTime}ms`);
 }, []); // Empty dependency array ensures this effect runs only once after the initial render

  return (
    <header>
      <h1>Counter App</h1>
    </header>
 );
}

// Main component of the Counter App
function CounterApp() {
  // State hook to manage the count value
  const [count, setCount] = useState(0);

  return (
    <>
      {/* Render the TimeToRender component */}
      <TimeToRender />
      <div>
        {/* Display the current count */}
        <p>{count}</p>
        {/* Button to increase the count */}
        <button onClick={() => setCount(count + 1)}>Increase</button>
        {/* Button to decrease the count */}
        <button onClick={() => setCount(count - 1)}>Decrease</button>
      </div>
    </>
 );
}

// Export the CounterApp component as the default export
export default CounterApp;
    

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

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

Процесс рендеринга компонента в компиляторе, а также время, необходимое для его повторного рендеринга
Процесс рендеринга компонента в компиляторе, а также время, необходимое для его повторного рендеринга

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

Время, требуемое для каждого повторного рендеринга всего компонента при использовании транспилятора
Время, требуемое для каждого повторного рендеринга всего компонента при использовании транспилятора

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

Ранее мы использовали хуки memo, useMemo() и useCallback() для достижения подобного эффекта, что делает эти хуки устаревшими. Новый подход позволяет нам писать более чистый и компактный код.

Новые Hooks

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

В React 19 было введено четыре новых хука:

  1. useTransition
  2. useActionState
  3. useFormStatus
  4. useOptimistic

useTransition

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

Это гарантирует плавную обработку обновлений при инициации пользователем. Кроме того, интерфейс будет точно отображать текущее состояние асинхронной функции. Давайте посмотрим на пример кода.

Приведенный ниже код представляет собой приложение, которое добавляет буквы алфавита в список. При нажатии кнопки "Добавить" происходит ожидание 4 секунды перед добавлением новой буквы в список. Мы можем автоматически управлять состоянием ожидания с помощью функции startTransition().

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

// Function to create a delay for a given number of milliseconds
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Asynchronous function to add an alphabet to the list after a delay
async function addAlphabetToList(alphabet) {
  await delay(4000);
  return null;
}

// Component to add an alphabet to a list
function AlphabetAdder() {
  const [alphabet, setAlphabet] = useState("");
  const [error, setError] = useState(null);
  // State and function for transition
  const [isPending, startTransition] = useTransition();
  const [alphabetList, setAlphabetList] = useState([]); // State to store the list of alphabets

  // Function to handle adding an alphabet to the list
  const handleAddAlphabet = async () => {
    startTransition(async () => {
      const error = await addAlphabetToList(alphabet); // Add alphabet to the list with delay
      if (error) {
        setError(error);
 } else {
        setAlphabetList([...alphabetList, alphabet]);
 }
 });
 };

  // Log the pending state
  console.log("Pending:", isPending);

  return <></>;
}
    

На изображении ниже показано, что состояние ожидания остается равным true до того момента, пока буква алфавита не будет добавлена в список через 4 секунды.

Состояние ожидания остается равным true до того момента, пока буква алфавита не будет добавлена в список через 4 секунды
Состояние ожидания остается равным true до того момента, пока буква алфавита не будет добавлена в список через 4 секунды

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

        import React, { useState } from "react";

// Function to create a delay
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Async function to simulate adding an alphabet to a list after a delay
async function addAlphabetToList(alphabet) {
  await delay(4000);
  return null;
}

function AlphabetAdder() {
  const [alphabet, setAlphabet] = useState("");
  const [error, setError] = useState(null);
  // State to manage the pending state
  const [isPending, setIsPending] = useState(false);
  const [alphabetList, setAlphabetList] = useState([]);

  // Function to handle adding the alphabet
  const handleAddAlphabet = async () => {
    setIsPending(true);
    // Add the alphabet to the list after the delay
    const error = await addAlphabetToList(alphabet);
    if (error) {
      setError(error);
 } else {
      setAlphabetList([...alphabetList, alphabet]);
 }
    setIsPending(false);
 };

  // Log the pending state
  console.log("Pending:", isPending);

  return <></>;
}
export default AlphabetAdder;
    

useActionState

Хук useActionState применяется для управления изменениями состояния в пользовательском интерфейсе, аналогично хуку useTransition. Отличие между ними заключается в том, что useActionState позволяет установить состояния error, action и pending в одной строке кода, вместо нескольких строк, как показано выше. Кроме того, хук useTransition обрабатывает только состояние ожидания.

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

        const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function addAlphabetToList(name) {
  await delay(4000);
  return null;
}

function AlphabetAdder() {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) => {
      const error = await addAlphabetToList(formData.get("name"));
      if (error) {
        return error;
 }

      return null;
 },
    null,
 );

  console.log("Pending:", isPending);
  return <></>;
}
    

В представленном выше коде хук useActionState используется для автоматической обработки состояний error, submitAction и isPending без явного определения через useState или useTransition для состояний, ожидающих обновления, как мы делали ранее.

useFormStatus

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

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

        function FormStatusButton() {
  const { isPending } = useFormStatus();
  return (
    <div>
      <button type="submit" disabled={isPending}>
        {isPending ? "Adding..." : "Add"}
      </button>
    </div>
 );
}
    

Ранее для доступа к состоянию ожидания мы передавали это состояние как пропс. В приведенном ниже примере кода мы передали состояние isPending как пропс в функцию CustomButton, чтобы иметь к нему доступ в операторе return.

        function CustomButton({ isPending }) {
  return (
    <div>
      <button type="submit" disabled={isPending}>
        {isPending ? "Adding..." : "Add"}
      </button>
    </div>
 );
}
    

useOptimistic

Этот хук позволяет нам обновлять пользовательский интерфейс немедленно при изменении определенного состояния, даже без ожидания завершения асинхронного действия. Например, в чат-приложениях, когда сообщение отправляется, оно мгновенно отображается в интерфейсе до его фактической отправки получателю. Часто добавляются индикаторы, такие как галочки, чтобы указать, когда сообщение будет полностью доставлено.

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

        function App({ initialTasks }) {
  // State to hold the tasks
  const [tasks, setTasks] = useState(initialTasks);
  // Optimistic UI state for tasks
  const [optimisticTask, addOptimisticTasks] = useOptimistic(tasks);

  const inputRef = useRef(null);

  // Handle form submission
  async function handleSubmit(e) {
    if (inputRef.current == null) return;

    // Create an optimistic task
    const optimisticTask = {
      id: crypto.randomUUID(),
      title: inputRef.current.value,
 };

    // Add the optimistic task to the state
    addOptimisticTasks((prev) => [...prev, optimisticTask]);

    // Create a new task (simulating server creation)
    const newTask = await createTask(inputRef.current.value);
    // Add the new task to the state
    setTasks((prev) => [...prev, newTask]);
 }

  return (
    <>
      <form action={handleSubmit}>
        <label>Add New Task</label>
        <br />
        <input ref={inputRef} required />
        <br />
        <button>Create Task</button>
      </form>
      <ul>
        {/* Render the list of optimistic tasks */}
        {optimisticTask.map((task) => (
          <li key={task.id}>{task.title}</li>
 ))}
      </ul>
    </>
 );
}

// Function that simulates creating a task on the server
function createTask(title) {
  return delay(
 { id: crypto.randomUUID(), title: `${title} - in the server` },
    4000,
 );
}

// Function to create a delay
function delay(value, duration) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), duration);
 });
}

export default App;
    

На данном изображении демонстрируется мгновенное обновление пользовательского интерфейса. Через 4 секунды отображаемая задача заменяется на актуальную задачу с сервера.

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

Ранее для решения этой задачи мы использовали хук useState для управления оптимистичным отображением задач и их дальнейшего обновления с фактическими данными.

        const [tasks, setTasks] = useState(initialTasks);
// Setting optimistic tasks using the useState hook
const [optimisticTasks, setOptimisticTasks] = useState(initialTasks);

const inputRef = useRef(null);

async function handleSubmit(e) {
  e.preventDefault(); // Prevent default form submission

  if (inputRef.current == null) return;

  const optimisticTask = {
    id: crypto.randomUUID(),
    title: inputRef.current.value,
 };

  // Add the optimistic task immediately
  setOptimisticTasks((prev) => [...prev, optimisticTask]);

  // Create the actual task with a delay
  const newTask = await createTask(inputRef.current.value);
  setTasks((prev) => [...prev, newTask]);
  setOptimisticTasks((prev) =>
    prev.map((task) => (task.id === optimisticTask.id ? newTask : task)),
 );
}
    

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

👨‍💻🎨 Библиотека фронтендера
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека фронтендера»

Новый API

API use позволяет использовать промисы и async-await. Он принимает только промис, а не функцию, и обновляет пользовательский интерфейс после завершения промиса. Вы можете вызывать этот API внутри операторов if и циклов.

В приведенном примере кода содержатся две асинхронные функции, которые имитируют задержку при получении данных, как это обычно происходит при отправке запросов на сервер. Первая функция возвращает список авторов после задержки в 4 секунды, а вторая – список блогов после задержки в 2 секунды. Затем пользовательский интерфейс обновляется этими значениями.

        import { use, Suspense } from "react";

// Create a delay
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Async function that fetches the list of blogs after the delay
const getBlogs = async () => {
  await delay(4000);
  return ["OpenReplay", "Devto", "Medium"];
};

// Async function that fetches the list of authors after a delay
const getAuthors = async () => {
  await delay(2000);
  return {
    isFetchUsers: true,
    authors: ["Mary Chidera", "Ugorji Marydera", "Ken Erics"],
 };
};

// Component to retrieve and display authors and blogs
function RetrieveAuthors({ authorsPromise, blogsPromise }) {
  // Use the authorsPromise to get the authors' data
  const { isFetchUsers, authors } = use(authorsPromise);

  console.log("Authors Promise Done: ", authors);

  let blogs;
  // If users are fetched, use the blogsPromise to get the blogs data
  if (isFetchUsers) blogs = use(blogsPromise);

  console.log("Blogs Promise is done :", blogs);

  return (
    <>
      <div>
        <h2>Authors</h2>
        <div>
          {/* Map over the authors array and display each author */}
          {authors.map((author, idx) => (
            <p key={idx}>{author}</p>
 ))}
        </div>
      </div>
      {blogs && (
        <div>
          <h2>Top Blogs</h2>
          <div>
            {/* Map over the blogs array and display each blog */}
            {blogs.map((blog, idx) => (
              <p key={idx}>{blog}</p>
 ))}
          </div>
        </div>
 )}
    </>
 );
}

// Main App component
function App() {
  // Create promises for authors and blogs
  const authorsPromise = getAuthors();
  const blogsPromise = getBlogs();

  return (
    // Use Suspense to handle loading states
    <Suspense fallback={<div>Loading Authors...</div>}>
      <RetrieveAuthors
        authorsPromise={authorsPromise}
        blogsPromise={blogsPromise}
      />
    </Suspense>
 );
}

export default App;
    

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

Пользовательский интерфейс обновляется только после того, как все промисы загрузились
Пользовательский интерфейс обновляется только после того, как все промисы загрузились

Улучшение передачи ref в пропсах

В React 19 теперь можно передавать ref как обычное свойство объекта, а не обязательно использовать forwardRef для передачи ref компоненту. Это позволяет сохранять значения ref между рендерингами и избегать дополнительных перерисовок компонента.

Вот пример новой реализации передачи ref как обычного свойства объекта:

        function NewRef({ ref }) {
  return <button ref={ref}>Click Me</button>;
}

//...
<NewRef ref={ref} />;
    

Ранее мы использовали forwardRef для определения компонента OldRef. Этот метод позволяет компоненту получить доступ к ref, передаваемому ему. Рассмотрим пример реализации:

        const OldRef = forwardRef((props, ref) => {
  return (
    <button ref={ref} {...props}>
      {props.children}
    </button>
 );
});

//...
<OldRef ref={ref} onClick={handleClick}>
 Click me
</OldRef>;
    

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

React 19 теперь поддерживает рендеринг тегов метаданных прямо в компоненте без использования сторонних библиотек, таких как react-helmet. В приведенном ниже коде мы обновляем элементы title и meta непосредственно в компоненте App:

        Назад
Что нового в React 19: 12 последних функций и обновлений
Марыдера Угорджи
Марыдера Угорджи
24 сен 2024 г. · 9 мин чтения

Что нового в React 19: 12 последних функций и обновлений
Наконец-то вышел React 19! Он принес с собой несколько интересных обновлений, начиная от компилятора React и заканчивая введением новых хуков, улучшенной системой отчетов об ошибках и т. д., и в этой статье мы рассмотрим около дюжины новейших функций React.

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

Компилятор React
Это возглавляет список как последнее дополнение к React. React 18 и ниже не имели компилятора; вместо этого они использовали транспилятор. Основное различие между ними заключается в том, что транспилятор перерисовывает весь компонент при изменении состояния, а компилятор — нет. Компилятор возвращает мемоизированный код. Таким образом, больше нет ненужного повторного рендеринга, и это заставило бы приложения React работать быстрее.

Давайте запустим приведенный ниже код в React 18 и React 19 и посмотрим на разницу в том, как происходит повторная визуализация компонентов.

// Component to measure and log the time taken to render the header
function TimeToRender() {
  // Record the start time when the component begins rendering
  const startTime = performance.now();

  useEffect(() => {
    // Record the end time when the component has rendered
    const endTime = performance.now();
    // Log the time taken to render the component
    console.log(`CustomHeader render time: ${endTime - startTime}ms`);
 }, []); // Empty dependency array ensures this effect runs only once after the initial render

  return (
    <header>
      <h1>Counter App</h1>
    </header>
 );
}

// Main component of the Counter App
function CounterApp() {
  // State hook to manage the count value
  const [count, setCount] = useState(0);

  return (
    <>
      {/* Render the TimeToRender component */}
      <TimeToRender />
      <div>
        {/* Display the current count */}
        <p>{count}</p>
        {/* Button to increase the count */}
        <button onClick={() => setCount(count + 1)}>Increase</button>
        {/* Button to decrease the count */}
        <button onClick={() => setCount(count - 1)}>Decrease</button>
      </div>
    </>
 );
}

// Export the CounterApp component as the default export
export default CounterApp;
Код выше — это приложение-счетчик, которое увеличивает и уменьшает счетчик при нажатии кнопки. У него есть функция TimeToRender, которая измеряет время, необходимое для рендеринга компонента каждый раз, когда он рендерится.

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

использовать (1)

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

компилятор (1)

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

Раньше мы бы использовали хуки memo , useMemo() и useCallback() для достижения этого. Это сделало бы эти хуки устаревшими. Кроме того, у нас был бы более чистый и меньше строк кода.

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

В React 19 было введено четыре новых хука:

Использование Transition Hook
Хук useActionState​
Хук useFormStatus​
ИспользованиеOptimistic Hook​
Использование Transition Hook
React 19 поддерживает использование asyncфункций в переходах для управления изменениями состояния, которые могут привести к изменениям пользовательского интерфейса. Вы можете использовать useTransitionхук для обновления статуса состояния, чтобы автоматически показать, находится ли оно в состоянии ожидания или нет.

Это гарантирует, что когда пользователь запускает обновление, оно обрабатывается гладко. Кроме того, пользовательский интерфейс будет отражать правильное состояние функции async. Давайте рассмотрим пример кода.

Код ниже — это приложение, которое добавляет алфавит в список. При нажатии кнопки добавления оно ждет 4 секунды, прежде чем добавить алфавит в список. Мы можем автоматически обрабатывать состояние ожидания с помощью startTransition asyncфункции.

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

// Function to create a delay for a given number of milliseconds
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Asynchronous function to add an alphabet to the list after a delay
async function addAlphabetToList(alphabet) {
  await delay(4000);
  return null;
}

// Component to add an alphabet to a list
function AlphabetAdder() {
  const [alphabet, setAlphabet] = useState("");
  const [error, setError] = useState(null);
  // State and function for transition
  const [isPending, startTransition] = useTransition();
  const [alphabetList, setAlphabetList] = useState([]); // State to store the list of alphabets

  // Function to handle adding an alphabet to the list
  const handleAddAlphabet = async () => {
    startTransition(async () => {
      const error = await addAlphabetToList(alphabet); // Add alphabet to the list with delay
      if (error) {
        setError(error);
 } else {
        setAlphabetList([...alphabetList, alphabet]);
 }
 });
 };

  // Log the pending state
  console.log("Pending:", isPending);

  return <></>;
}
На изображении ниже показано, что состояние ожидания сохраняется trueдо тех пор, пока алфавит не будет добавлен в список через 4 с.

использованиеперехода

Это контрастирует с предыдущими версиями, где мы обрабатывали состояние ожидания вручную, используя setIsPendingdingфункцию для установки состояния в значение trueили false.

import React, { useState } from "react";

// Function to create a delay
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Async function to simulate adding an alphabet to a list after a delay
async function addAlphabetToList(alphabet) {
  await delay(4000);
  return null;
}

function AlphabetAdder() {
  const [alphabet, setAlphabet] = useState("");
  const [error, setError] = useState(null);
  // State to manage the pending state
  const [isPending, setIsPending] = useState(false);
  const [alphabetList, setAlphabetList] = useState([]);

  // Function to handle adding the alphabet
  const handleAddAlphabet = async () => {
    setIsPending(true);
    // Add the alphabet to the list after the delay
    const error = await addAlphabetToList(alphabet);
    if (error) {
      setError(error);
 } else {
      setAlphabetList([...alphabetList, alphabet]);
 }
    setIsPending(false);
 };

  // Log the pending state
  console.log("Pending:", isPending);

  return <></>;
}
export default AlphabetAdder;
Хук useActionState
Хук useActionStateиспользуется для управления изменениями состояния в пользовательском интерфейсе, как и useTransitionхук. Что отличает его от useTransitionхука выше, так это то, что он назначает состояния error, action, и pendingв одной строке кода вместо нескольких строк, как показано выше. Кроме того, useTransitionхук обрабатывает только состояние ожидания.

Мы можем переписать код выше, используя useActionState, что даст нам меньше строк кода.

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function addAlphabetToList(name) {
  await delay(4000);
  return null;
}

function AlphabetAdder() {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) => {
      const error = await addAlphabetToList(formData.get("name"));
      if (error) {
        return error;
 }

      return null;
 },
    null,
 );

  console.log("Pending:", isPending);
  return <></>;
}
В приведенном выше коде useActionStateхук используется для автоматической обработки состояний error, submitActionи isPendingбез явного определения его с помощью useStateхука или useTransitionхука для ожидающих состояний, как мы делали ранее.

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

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

function FormStatusButton() {
  const { isPending } = useFormStatus();
  return (
    <div>
      <button type="submit" disabled={isPending}>
        {isPending ? "Adding..." : "Add"}
      </button>
    </div>
 );
}
Раньше для доступа к состоянию ожидания мы бы передавали состояние с помощью prop. В примере кода ниже мы передали isPendingсостояние как свойство функции CustomButtonдля доступа к нему в операторе return.

function CustomButton({ isPending }) {
  return (
    <div>
      <button type="submit" disabled={isPending}>
        {isPending ? "Adding..." : "Add"}
      </button>
    </div>
 );
}
ИспользованиеOptimistic Hook
Этот хук позволяет нам мгновенно обновлять пользовательский интерфейс при изменении некоторого состояния, не дожидаясь asyncзавершения действия. Например, в чат-приложениях, когда отправляется сообщение, оно немедленно обновляется в пользовательском интерфейсе, даже до того, как оно будет отправлено получателю, и в большинстве случаев добавляются галочки, чтобы знать, когда оно полностью доставлено.

Код ниже иллюстрирует приложение задач, где при добавлении задачи в список задач она проходит через asyncфункцию и, наконец, обновляется в пользовательском интерфейсе через 4 с. Мы используем useOptimisticхук для немедленного отображения добавленной задачи в пользовательском интерфейсе, даже до того, как закончится 4-секундная продолжительность.

function App({ initialTasks }) {
  // State to hold the tasks
  const [tasks, setTasks] = useState(initialTasks);
  // Optimistic UI state for tasks
  const [optimisticTask, addOptimisticTasks] = useOptimistic(tasks);

  const inputRef = useRef(null);

  // Handle form submission
  async function handleSubmit(e) {
    if (inputRef.current == null) return;

    // Create an optimistic task
    const optimisticTask = {
      id: crypto.randomUUID(),
      title: inputRef.current.value,
 };

    // Add the optimistic task to the state
    addOptimisticTasks((prev) => [...prev, optimisticTask]);

    // Create a new task (simulating server creation)
    const newTask = await createTask(inputRef.current.value);
    // Add the new task to the state
    setTasks((prev) => [...prev, newTask]);
 }

  return (
    <>
      <form action={handleSubmit}>
        <label>Add New Task</label>
        <br />
        <input ref={inputRef} required />
        <br />
        <button>Create Task</button>
      </form>
      <ul>
        {/* Render the list of optimistic tasks */}
        {optimisticTask.map((task) => (
          <li key={task.id}>{task.title}</li>
 ))}
      </ul>
    </>
 );
}

// Function that simulates creating a task on the server
function createTask(title) {
  return delay(
 { id: crypto.randomUUID(), title: `${title} - in the server` },
    4000,
 );
}

// Function to create a delay
function delay(value, duration) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), duration);
 });
}

export default App;
На изображении ниже показано, как пользовательский интерфейс обновляется немедленно. Через 4 секунды задача заменяется на ту, что на сервере.

использованиеперехода (1)

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

const [tasks, setTasks] = useState(initialTasks);
// Setting optimistic tasks using the useState hook
const [optimisticTasks, setOptimisticTasks] = useState(initialTasks);

const inputRef = useRef(null);

async function handleSubmit(e) {
  e.preventDefault(); // Prevent default form submission

  if (inputRef.current == null) return;

  const optimisticTask = {
    id: crypto.randomUUID(),
    title: inputRef.current.value,
 };

  // Add the optimistic task immediately
  setOptimisticTasks((prev) => [...prev, optimisticTask]);

  // Create the actual task with a delay
  const newTask = await createTask(inputRef.current.value);
  setTasks((prev) => [...prev, newTask]);
  setOptimisticTasks((prev) =>
    prev.map((task) => (task.id === optimisticTask.id ? newTask : task)),
 );
}
В приведенном выше коде используется useStateхук для добавления optimisticTasksоптимистичных обновлений пользовательского интерфейса и его функция-установщик setOptimistictask для обновления состояния.

Новый API - используйте
API use позволяет использовать Promisesи async-await. Он принимает только Promise, а не функцию, когда Promiseзавершает работу до обновления пользовательского интерфейса. Вы можете вызывать этот API внутри операторов if и циклов. Вы также можете вызывать этот API внутри операторов if и циклов.

Давайте рассмотрим пример кода. Он содержит 2 asyncфункции, которые имитируют выборку данных с задержкой, как это обычно бывает при отправке данных на сервер. Он возвращает список авторов и блогов после задержки в 4 и 2 секунды соответственно, перед обновлением пользовательского интерфейса этими значениями.

import { use, Suspense } from "react";

// Create a delay
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Async function that fetches the list of blogs after the delay
const getBlogs = async () => {
  await delay(4000);
  return ["OpenReplay", "Devto", "Medium"];
};

// Async function that fetches the list of authors after a delay
const getAuthors = async () => {
  await delay(2000);
  return {
    isFetchUsers: true,
    authors: ["Mary Chidera", "Ugorji Marydera", "Ken Erics"],
 };
};

// Component to retrieve and display authors and blogs
function RetrieveAuthors({ authorsPromise, blogsPromise }) {
  // Use the authorsPromise to get the authors' data
  const { isFetchUsers, authors } = use(authorsPromise);

  console.log("Authors Promise Done: ", authors);

  let blogs;
  // If users are fetched, use the blogsPromise to get the blogs data
  if (isFetchUsers) blogs = use(blogsPromise);

  console.log("Blogs Promise is done :", blogs);

  return (
    <>
      <div>
        <h2>Authors</h2>
        <div>
          {/* Map over the authors array and display each author */}
          {authors.map((author, idx) => (
            <p key={idx}>{author}</p>
 ))}
        </div>
      </div>
      {blogs && (
        <div>
          <h2>Top Blogs</h2>
          <div>
            {/* Map over the blogs array and display each blog */}
            {blogs.map((blog, idx) => (
              <p key={idx}>{blog}</p>
 ))}
          </div>
        </div>
 )}
    </>
 );
}

// Main App component
function App() {
  // Create promises for authors and blogs
  const authorsPromise = getAuthors();
  const blogsPromise = getBlogs();

  return (
    // Use Suspense to handle loading states
    <Suspense fallback={<div>Loading Authors...</div>}>
      <RetrieveAuthors
        authorsPromise={authorsPromise}
        blogsPromise={blogsPromise}
      />
    </Suspense>
 );
}

export default App;
На изображении ниже показано, как пользовательский интерфейс обновляется только после того, как все Promisesзагрузилось.

использовать

Ссылка как обычный реквизит
Ссылки позволяют напрямую получать доступ к элементам DOM и взаимодействовать с ними. Они сохраняют свои значения между рендерингами и не вызывают повторного рендеринга компонента.

В React 19 теперь можно передавать refкак обычный объект propвместо использования forwardRefдля передачи a refкомпоненту.

Вот новая реализация refобычного prop.

function NewRef({ ref }) {
  return <button ref={ref}>Click Me</button>;
}

//...
<NewRef ref={ref} />;
Вот как мы бы реализовали это ранее. Как показано ниже, мы используем forwardRefдля определения OldRefкомпонента. The forwardRefпринимает propsи a ref. refТеперь компонент может получить к нему доступ button.

const OldRef = forwardRef((props, ref) => {
  return (
    <button ref={ref} {...props}>
      {props.children}
    </button>
 );
});

//...
<OldRef ref={ref} onClick={handleClick}>
 Click me
</OldRef>;
Действие
В формах теперь можно передавать свойство действия . Это propможет быть asyncфункция или URL. Форма будет выполняться action propпри отправке формы.

Мы также можем получить доступ к полям ввода и элементам формы, передав их formDataфункции и используя getAPI.

function Form() {
  const addBlogName = (formData) => {
    const blogName = formData.get("blogName");
    console.log(`You submitted '${blogName}'`);
 };

  return (
    <form action={addBlogName}>
      <input name="blogName" />
      <button type="submit">Search</button>
    </form>
 );
}

export default Form;
Раньше мы бы получили доступ к переданному имени, используя event.

const addBlogName = (event) => {
  event.preventDefault();
  const blogName = event.target.elements.blogName.value;
  console.log(`You submitted '${blogName}'`);
};
В приведенной выше функции addBlogNameмы напрямую обращаемся к значению во входном элементе, используя blogNameдля event.target.elementsдоступа к элементу и его свойству.

Контекст как поставщик
Теперь вы можете напрямую использовать контекст в качестве поставщика в вашем коде. Раньше, когда бы мы ни хотели вызвать context, нам приходилось делать contextName.Provider, а затем передавать потомков. Теперь нам больше не нужно этого делать, поскольку мы можем напрямую вызывать только имя без .Provider.

В приведенном ниже коде мы создали контекст с именем lightModeContextи вызвали его непосредственно в операторе return.

//Context we created
const lightModeContext = createContext({ theme: "light" });

//New method of calling the context directly as a provider
function App({ children }) {
  return <lightModeContext value="dark">Children</lightModeContext>;
}
Раньше нам пришлось бы получать доступ к созданному нами контексту, вызывая lightModeContext.Provider.

//old method of calling the context with the .Provider
function App({ children }) {
  return <lightModeContext.Provider value="light"></lightModeContext.Provider>;
}
Поддержка метаданных документа
React 19 поддерживает рендеринг тегов метаданных в компоненте. Раньше мы бы делали это с помощью сторонних библиотек, таких как react-helmet .

В приведенном ниже коде мы напрямую обновляем элементы titleи metaв Appкомпоненте.

function App({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      {/*meta tags */}
      <title>{title}</title>
      <meta name="description" content="post" />
      <meta name="keywords" content={post.keywords} />
    </article>
 );
}
    

Вот пример того, как мы делали это в более ранних версиях с использованием библиотеки react-helmet. В этом примере мы обновляли элементы title и meta через react-helmet:

        import React from 'react';
import { Helmet } from 'react-helmet';

function App({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <Helmet>
        <title>{post.title}</title>
        <meta name="description" content={post.description} />
        <meta name="keywords" content={post.keywords} />
      </Helmet>
      {/* Other content of your component */}
    </article>
 );
}

export default App;
    

В приведенном выше коде для установки элементов title и meta мы использовали компонент Helmet из библиотеки react-helmet.

Улучшенные отчеты об ошибках

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

Ошибка, возникшая в компоненте ErrorComponent в React 18
Ошибка, возникшая в компоненте ErrorComponent в React 18

Вот изображение, демонстрирующее ту же ошибку, как и в React 19, но представленную в отчете React 18.

Ошибка, возникшая в компоненте ErrorComponent в React 19
Ошибка, возникшая в компоненте ErrorComponent в React 19

Заключение

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

МЕРОПРИЯТИЯ

Комментарии

 
 
07 октября 2024

Статья топ, на мой взгляд. Отдельное спасибо за гифки!

ВАКАНСИИ

Добавить вакансию

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

LIVE >

Подпишись

на push-уведомления