Как стать автором
Обновить

React 18

Время на прочтение 16 мин
Количество просмотров 19K
Автор оригинала: The React Team

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

Наша последняя мажорная версия включает в себя такие улучшения, как автоматическое пакетирование, новые API, такие как startTransition, и потоковый серверный рендеринг с поддержкой Suspense.

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

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

Если вы пропустили, мы поделились этим видением на React Conf 2021:

Ниже приведен полный обзор того, что ожидается в этом релизе, начиная с Concurrent Rendering.

Примечание для пользователей React Native: React 18 будет поставляться в React Native с New React Native Architecture. Для получения дополнительной информации смотрите React Conf keynote here.

Что такое Concurrent React?

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

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

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

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

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

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

Другой пример - многократно используемое состояние. Concurrent React может удалять части пользовательского интерфейса с экрана, а затем добавлять их обратно, повторно используя предыдущее состояние. Например, когда пользователь уходит с текущего экрана и возвращается обратно, React должен иметь возможность восстановить предыдущий экран в том же состоянии, в котором он был до этого. В одном из ближайших обновлений мы планируем добавить новый компонент под названием <Offscreen>, который реализует этот паттерн. Аналогично, вы сможете использовать Offscreen для подготовки нового пользовательского интерфейса в фоновом режиме, чтобы он был готов до того, как пользователь откроет его.

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

Постепенное внедрение функций конкурентного рендеринга

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

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

Общая стратегия обновления заключается в том, чтобы заставить ваше приложение работать на React 18, не ломая существующий код. Затем вы можете постепенно начать добавлять конкурентные возможности в своем собственном темпе. Вы можете использовать <StrictMode>, чтобы помочь выявить ошибки, связанные с конкурентностью, во время разработки. Строгий режим не влияет на поведение в эксплуатации, но во время разработки он будет выдавать дополнительные предупреждения и дважды вызывать функции, которые должны быть идемпотентными. Он не отлавливает всё, но эффективно предотвращает наиболее распространенные типы ошибок.

После обновления до React 18 вы сможете сразу же начать использовать функционалы конкурентной работы. Например, вы можете использовать startTransition для перехода между экранами, не блокируя ввод данных пользователем. Или useDeferredValue для уменьшения частоты дорогостоящего повторного рендеринга.

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

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

Для получения дополнительной информации см. наш предыдущий пост: Как перейти на React 18.

Suspense во фреймворках данных

В React 18 вы можете начать использовать Suspense для выборки данных в специализированных фреймворках, таких как Relay, Next.js, Hydrogen или Remix. Ситуативная выборка данных с помощью Suspense технически возможна, но все же не рекомендуется в качестве общей стратегии.

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

Как и в предыдущих версиях React, вы также можете использовать Suspense для разделения кода на клиенте с помощью React.lazy. Но наше видение Suspense всегда было связано с гораздо большим, чем загрузка кода - цель заключается в расширении поддержки Suspense, чтобы в конечном итоге один и тот же декларативный fallback Suspense мог обрабатывать любые асинхронные операции (загрузка кода, данных, изображений и т.д.).

Серверные компоненты все еще в разработке

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

Server Components все еще являются экспериментальными, но мы ожидаем выпустить начальную версию в минорном релизе 18.x. Тем временем мы работаем с такими фреймворками, как Next.js, Hydrogen и Remix, чтобы продвинуть то что мы предлагаем и подготовить к широкому внедрению.

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

Новый функционал: Automatic Batching (автоматическое пакетирование)

Пакетирование (batching) - это когда React группирует несколько обновлений состояния в один повторный рендеринг для повышения производительности. В отсутствии автоматического пакетирования мы пакетировали только обновления внутри обработчиков событий React. Обновления внутри промисов, setTimeout, нативных обработчиках событий или любые другие события в React по умолчанию не пакетировались. При автоматическом пакетировании эти обновления будут пакетироваться автоматически:

// Раньше: только события React пакетировались
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React выполнит рендеринг дважды, один для каждого обновления состояния (т.е. пакетирование отсутствует)
}, 1000);

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

Для получения дополнительной информации смотрите этот пост Automatic batching for fewer renders in React 18.

Новый функционал: Transitions (переходы)

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

  • Срочные обновления отражают прямое взаимодействие, например, ввод текста, клик, нажатие клавиши и так далее.

  • Переходные обновления переводят пользовательский интерфейс из одного представления в другое.

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

Например, когда вы выбираете фильтр в выпадающем списке, вы ожидаете, что сама кнопка фильтра будет немедленно реагировать на ваш щелчок. Однако фактические результаты могут появляться (transition) позднее. Небольшая задержка будет незаметна и часто ожидаема. И если вы снова измените фильтр до того, как результаты будут отображены, вы увидите только последние результаты.

Как правило, для наилучшего пользовательского опыта один пользовательский ввод должен приводить как к срочному обновлению, так и к не срочному. Вы можете использовать API startTransition внутри обработчика события ввода, чтобы сообщить React, какие обновления являются срочными, а какие "переходными":

import {startTransition} from 'react';

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

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

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

  • useTransition: хук для запуска переходов, включающий значение для отслеживания состояния ожидания.

  • startTransition: метод для запуска переходов, когда хук не может быть использован (прим. переводчика - в классовых компонентах).

Переходы согласуются с конкурентным рендерингом, что позволяет прервать обновление. Если контент повторно запрашивается (re-suspend), переходы в свою очередь говорят React продолжать показывать текущий контент, в то время как контент перехода рендерится в фоновом режиме (более подробную информацию см. в Suspense RFC).

См. документацию по переходам здесь.

Новые функциональные возможности Suspense

Suspense позволяет вам декларативно отображать состояние загрузки для части дерева компонентов, если она еще не готова к отображению:

<Suspense fallback={<Spinner />}>
  <Comments />
</Suspense>

Suspense делает "состояние загрузки пользовательского интерфейса" first-class декларативной концепцией в модели программирования React. Это позволяет нам создавать на его основе функциональность более высокого уровня.

Мы представили ограниченную версию Suspense несколько лет назад. Однако единственным поддерживаемым вариантом использования было разделение кода с помощью React.lazy, а при рендеринге на сервере оно вообще не поддерживалось.

В React 18 мы добавили поддержку Suspense на сервере и расширили его возможности за счет возможностей параллельного рендеринга.

Suspense в React 18 лучше всего работает в сочетании с API переходов. Если вы приостановите (suspend) работу во время перехода, React предотвратит замену индикатором загрузки уже видимого содержимого. Вместо этого React отложит рендеринг до тех пор, пока не загрузится достаточно данных, чтобы предотвратить ненужное состояние загрузки.

Подробнее см. RFC для Suspense in React 18.

Новые API клиентского и серверного рендеринга

В этом релизе мы воспользовались возможностью переработать API, которые мы предоставляем для рендеринга на клиенте и сервере. Эти изменения позволяют пользователям продолжать использовать старые API в режиме React 17, пока они переходят на новые API в React 18.

React DOM Client

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

  • createRoot: Новый метод для создания корня для render или unmount. Используйте его вместо ReactDOM.render. Новые возможности в React 18 не работают без этого

  • hydrateRoot: Новый метод для гидратации приложения отрендеренного на сервере. Используйте его вместо ReactDOM.hydrate в сочетании с новыми API React DOM Server. Новые возможности в React 18 не работают без него.

И createRoot и hydrateRoot принимают новую опцию onRecoverableError на случай, если вы хотите получать уведомления, когда React восстанавливается после ошибок во время рендеринга или гидратации для логирования. По умолчанию React будет использовать reportError, или console.error в старых браузерах.

См. документацию по React DOM Client здесь.

React DOM Server

Эти новые API теперь экспортируются из react-dom/server и имеют полную поддержку для потоковой передачи Suspense на сервере:

  • renderToPipeableStream: для потоковой передачи в окружениях Node

  • renderToReadableStream: для современных edge runtime окружений, таких как Deno и Cloudflare workers

Существующий метод renderToString продолжает работать, но использовать его не рекомендуется.

См. документацию по React DOM Server здесь.

Новые поведения в строгом режиме

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

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

Чтобы помочь устранить эти проблемы, React 18 вводит новую проверку в Strict Mode, предназначенную только для этапа разработки. Эта новая проверка будет автоматически размонтировать и повторно монтировать каждый компонент, всегда когда компонент монтируется в первый раз, восстанавливая предыдущее состояние при втором монтировании.

До этого изменения React монтировал компонент и создавал эффекты:

* React монтирует компонент
  * создаются layout эффекты 
  * создаются эффекты

В режиме Strict Mode в React 18, React будет симулировать размонтирование и повторное монтирование компонента в режиме разработки:

* React монтирует компонент
  * создаются layout эффекты
  * создаются эффекты
* React симулирует размонтирование компонента
  * layout эффекты уничтожаются
  * эффекты уничтожаются
* React симулирует монтирование компонента с предыдущим состоянием
  * создаются layout эффекты
  * создаются эффекты

См. документацию по обеспечению переиспользуемого состояния здесь.

Новые хуки

useId

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

Примечание

useId не предназначен для генерации ключей списка. Ключи должны генерироваться из ваших данных.

useTransition

useTransition и startTransition позволяют пометить некоторые обновления состояния как не срочные. Другие обновления состояния считаются срочными по умолчанию. React позволяет срочным обновлениям состояния (например, обновление текстового ввода) прерывать не срочные обновления состояния (например, вывод списка результатов поиска). См. документацию здесь

useDeferredValue

Функция useDeferredValue позволяет отложить пере-рендеринг не срочной части дерева. Это похоже на debouncing, но имеет несколько преимуществ по сравнению с ним. Нет фиксированной временной задержки, поэтому React будет пытаться выполнить отложенный рендеринг сразу после того, как первый рендеринг будет отражен на экране. Отложенный рендеринг прерывается и не блокирует ввод пользователя. См. документацию здесь

useSyncExternalStore

useSyncExternalStore - это новый хук, который позволяет внешним хранилищам (stores) поддерживать конкурентное чтение, принуждая обновления хранилища быть синхронными. Он устраняет необходимость в useEffect при реализации подписок на внешние источники данных и рекомендуется для любой библиотеки, которая интегрируется с внешним для React состоянием. См. документацию здесь.

Примечание

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

useInsertionEffect

useInsertionEffect - это новый хук, который позволяет библиотекам CSS-in-JS решать проблемы производительности инъекции стилей при рендеринге. Если вы еще не создали библиотеку CSS-in-JS, мы не ожидаем, что вы когда-либо будете использовать этот хук. Этот хук будет запущен после того, как DOM будет изменен, но до того, как эффекты размещения (layout effect) прочитают новое размещение. Это решает проблему, которая уже существует в React 17 и ниже, но становится еще более важной в React 18, поскольку React пропускает вперёд браузер во время параллельного рендеринга, давая ему шанс пересчитать размещение. См. документацию здесь

Примечание

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

Как обновиться

Смотрите How to Upgrade to React 18 для получения пошаговых инструкций и полного списка важных и значимых изменений.

Список изменений

React

  • Добавлены useTransition и useDeferredValue для разделения срочных обновлений от переходов.(#10426#10715#15593#15272#15578#15769#17058#18796#19121#19703#19719#19724#20672#20976 от @acdlite@lunaruan@rickhanlonii, и @sebmarkbage)

  • Добавлен useId для генерации уникальных IDs. (#17322#18576#22644#22672#21260 от @acdlite@lunaruan, и @sebmarkbage)

  • Добавлен useSyncExternalStore для помощи библиотекам внешних хранилищ в интеграции с React. (#15022#18000#18771#22211#22292#22239#22347#23150 от @acdlite@bvaughn, и @drarmstr)

  • Добавлен startTransition как вариант useTransition без ожидающего feedback. (#19696 от @rickhanlonii)

  • Добавлен useInsertionEffect для CSS-in-JS библиотек. (#21913 от @rickhanlonii)

  • Сделали Suspense перемонтирования эффектов размещения при повторном отображении контента. (#19322#19374#19523#20625#21079 от @acdlite@bvaughn, и @lunaruan)

  • Сделали эффекты перезапуска <StrictMode> для проверки восстановимости состояния. (#19523 , #21418 от @bvaughn и @lunaruan)

  • Assume Symbols всегда доступны. (#23348 от @sebmarkbage)

  • Удалили полифил object-assign. (#23351 от @sebmarkbage)

  • Удалили неподдерживаемое API unstable_changedBits. (#20953 от @acdlite)

  • Разрешили компонентам рендерить undefined. (#21869 от @rickhanlonii)

  • Синхронно смываем useEffect, возникающий в результате дискретных событий, таких как клики. (#21150 от @acdlite)

  • Suspense fallback={undefined} теперь ведет себя так же, как null и не игнорируется. (#21854 от @rickhanlonii)

  • Теперь все lazy(), преобразующиеся в один и тот же компонент, считаются эквивалентными. (#20357 от @sebmarkbage)

  • Не обновляем консоль во время первого рендера. (#22308 от @lunaruan)

  • Улучшение использования памяти. (#21039 от @bgirard)

  • Улучшение сообщений, если при когеренции строк возникают ошибки (Temporal.*, Symbol и т.д.) (#22064 от @justingrant)

  • Используем setImmediate, если доступно через MessageChannel. (#20834 от @gaearon)

  • Исправление того, что контекст не распространялся внутри приостановленных (suspended) деревьев. (#23095 от @gaearon)

  • Исправление useReducer, отслеживающего (observing) некорректные пропсы, путем удаления механизма eager bailout. (#22445 от @josephsavona)

  • Исправление игнорирования setState в Safari при добавлении iframes. (#23111 от @gaearon)

  • Исправление падения при рендеринге ZonedDateTime в дереве. (#20617 от @dimaqq)

  • Исправление падения при установке значения null для документа в тестах. (#22695 от @SimenB)

  • Исправление того, что onLoad не срабатывает, когда включены функционалы конкурентности. (#23316 от @gnoff)

  • Исправление предупреждения, когда селектор возвращает NaN. (#23333 от @hachibeeDI)

  • Исправление падения, когда в тестах документ имеет значение null. (#22695 от @SimenB)

  • Исправление генерируемого заголовка лицензии. (#23004 от @vitaliemiron)

  • Добавили package.json в качестве одной из точек входа. (#22954 от @Jack)

  • Разрешили приостановку за пределами Suspense boundary. (#23267 от @acdlite)

  • Записываем в журнал восстанавливаемую ошибку при сбое гидратации. (#23319 от @acdlite)

React DOM

React DOM Server

  • Добавили новый потоковый рендерер. (#14144#20970#21056#21255#21200#21257#21276#22443#22450#23247#24025#24030 от @sebmarkbage)

  • Исправили контекстные провайдеры в SSR при обработке нескольких запросов. (#23171 от @frandiox)

  • Возврат к клиентскому рендеру при несовпадении текста. (#23354 от @acdlite)

  • Пометили устаревшим renderToNodeStream. (#23359 от @sebmarkbage)

  • Исправили ложный журнал ошибок в новом серверном рендерере. (#24043 от @eps1lon)

  • Исправили ошибки в новом серверном рендерере. (#22617 от @shuding)

  • Игнорируем значения функций и символов внутри пользовательских элементов на сервере. (#21157 от @sebmarkbage)

Утилиты React DOM Test

  • Бросание исключения при использовании act в режиме эксплуатации. (#21686 от @acdlite)

  • Поддержка отключения ложных предупреждений с помощью global.IS_REACT_ACT_ENVIRONMENT. (#22561 от @acdlite)

  • Расширение act warning для охвата всех API, которые могут планировать работу React. (#22607 от @acdlite)

  • Сделали act пакетируемым обновлением. (#21797 от @acdlite)

  • Убрали предупреждение о висячих пассивных эффектах. (#22609 от @acdlite)

React Refresh

  • Отслеживание поздно смонтированных roots в Fast Refresh. (#22740 от @anc95)

  • Добавили поле exports в package.json. (#23087 от @otakustay)

Серверные компоненты (экспериментально)

  • Добавили поддержку Server Context. (#23244 от @salazarm)

  • Добавили поддержку lazy. (#24068 от @gnoff)

  • Обновление плагина webpack до webpack 5 (#22739 от @michenly)

  • Исправили ошибку в загрузчике Node. (#22537 от @btea)

  • Используем globalThis вместо window для edge окружений. (#22777 от @huozhi)

БОНУС ОТ ПЕРЕВОДЧИКА

видео на ту же тему от Михаила Непомнящего

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

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн