312

React 18 vs 16

Для начала, что есть нового в React 17

Изменение в делегировании событий

React использует прием делегирования событий и привязывает все события к объекту document. Таким образом, достигается повышение производительности.

Но если на странице используется несколько версий React, микрофронтенды или часть функционала работает на jQuery, то возникают проблемы. Такое поведение ломает event.stopPropagation(): если вложенное дерево остановило распространение (propagation) события, внешнее дерево все равно получит его.

Теперь все обработчики крепятся к корневому элементу, а не объекту document:

const rootNode = document.getElementById('root'); // <-- вот сюда
ReactDOM.render(<App />, rootNode);

Убран костыль с Синтетическим событием SyntheticEvent

Убрана оптимизация событий, которая более не актуальна в современных браузерах.

function handleChange(event) {
  // это работает в 16 React только если добавить event.persist()
  setData(data => ({
    ...data,
    // This crashes in React 16 and earlier:
    text: event.target.value
  }));
}

Теперь такой код не будет валиться с ошибкой, и нет нужды писать event.persist()

Для обратной совместимости эта функция оставлена в качестве заглушки.

Ближе к браузерам

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

Полностью асинхронный useEffect

useEffect(() => {
  // This is the effect itself.
return () => {
    // This is its cleanup.
  };
});

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

Для синхронной работы, можно по-прежнему использовать useLayoutEffect(), который остался незатронутым.

Ошибки при возвращении undefined

Это изменение довольно минорно, но повышает консистентность ошибок.

Если раньше нельзя было возвращать undefined только в обычных компонентах, то теперь такая ошибка будет выбрасываться еще и в React.forwardRef и React.memo.

let Button = forwardRef(() => {
  // We forgot to write return, so this component returns undefined.
  // React 17 surfaces this as an error instead of ignoring it.
  <button />;
});

let Button = memo(() => {
  // We forgot to write return, so this component returns undefined.
  // React 17 surfaces this as an error instead of ignoring it.
  <button />;
});

Улучшенный стек вызовов при ошибках

Когда выбрасывается ошибка, браузер показывает stack trace с названиями функций и их местоположения. Довольно трудно по этому стеку вызовов понять, в каком месте и в каком компоненте была вызвана ошибка на самом деле.

Теперь спользуется новых механизм по генерации стека вызовов, что позволяет увидеть дерево React-компонентов, которое привело к ошибке, даже в production-среде.

Удаление приватных экспортов

Последнее изменение — это удаление некоторых внутренних экспортов, которые ранее были открыты наружу.

Далее про React 18

Конкурентный режим (Concurrency)

Конкурентность - это не функциональность, как таковая. Это новый закулисный механизм, который позволяет React подготавливать несколько версий пользовательского интерфейса одновременно.

Автоматическая пакетная обработка (Batching)

React группирует несколько обновлений состояния в один повторный рендеринг для повышения производительности. В отсутствии автоматического пакетирования мы пакетировали только обновления внутри обработчиков событий React. Также и для промисов, setTimeout, чего не было в 17 версии.

// Теперь: обновления внутри setTimeout, промисах, нативных обработчиках событий или любые другие события - пакетируются
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React выполнит только один пере-рендер в конце (это и есть пакетирование!)
}, 1000);

Batching работает по дефолту, но его можно отключить. Для этого используется функция flushSync.

import { flushSync } from 'react-dom';

flushSync(() => {
	setCount(c => c + 1);
  setFlag(f => !f);
});

Переходы (Transitions)

Переход - это новая концепция в React для разграничения срочных и не срочных обновлений.

import { startTransition } from 'react';

// Срочно: Отобразить то что было введено пользователем
setInputValue(input);

// Любые изменения состояния внутри помечаются как переходные
startTransition(() => {
  // Transition: Show the results
  setSearchQuery(input);
});

Обновления, обернутые в startTransition, обрабатываются как не срочные и будут прерваны, если поступят более срочные обновления, такие как щелчки или нажатия клавиш.

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

React DOM Client

Новое API теперь экспортируются из react-dom/client:

import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

import { App } from './app';

const container = document.getElementById('app');
if (!container) {
  throw new Error('Failed to find the root element');
}
const root = createRoot(container);

root.render(
  <StrictMode>
    <App />
  </StrictMode>,
);

Новые хуки

Прочее

Напоследок немного о React 19

React получил новую бета-версию React 19. Это предварительная версия позволяет библиотекам адаптироваться к грядущим изменениям и подготовить совместимость перед официальным выпуском.

React Compiler

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

Offscreen (переименовано в Activity)

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

Actions (Действия), новый хук useActionState

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

function ChangeName({ name, setName }) {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) => {
      const error = await updateName(formData.get("name"));
      if (error) {
        return error;
      }
      redirect("/path");
      return null;
    },
    null,
  );

  return (
    <form action={submitAction}>
      <input type="text" name="name" />
      <button type="submit" disabled={isPending}>Update</button>
      {error && <p>{error}</p>}
    </form>
  );
}

Хук useOptimistic

Помогает оптимистично отображать данные, пока идет асинхронный запрос.

function ChangeName({currentName, onUpdateName}) {
  const [optimisticName, setOptimisticName] = useOptimistic(currentName);

  const submitAction = async formData => {
    const newName = formData.get("name");
    setOptimisticName(newName);
    const updatedName = await updateName(newName);
    onUpdateName(updatedName);
  };

  return (
    <form action={submitAction}>
      <p>Your name is: {optimisticName}</p>
      <p>
        <label>Change Name:</label>
        <input
          type="text"
          name="name"
          disabled={currentName !== optimisticName}
        />
      </p>
    </form>
  );
}

Новый API use

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

import {use} from 'react';

function Comments({commentsPromise}) {
  // `use` will suspend until the promise resolves.
  const comments = use(commentsPromise);
  return comments.map(comment => <p key={comment.id}>{comment}</p>);
}

function Page({commentsPromise}) {
  // When `use` suspends in Comments,
  // this Suspense boundary will be shown.
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  )
}

React Server Components и Server Actions:

Предоставляют новые возможности предварительной обработки компонентов и выполнения асинхронных действий на стороне сервера. Это позволяет передавать данные между клиентом и сервером более эффективно и с минимальными затратами по времени.

Улучшения и исправления:

Статьи:

React 17: Ничего нового?

Основные изменения React 18

React 18. Что нового?

React 18

Новые возможности и улучшения в React 19 Beta: обзор обновлений и оптимизация перехода

React 19 уничтожит все фреймворки