Suspense¶
<Suspense>
позволяет отображать фалбэк до тех пор, пока его дочерние элементы не закончат загрузку.
1 2 3 |
|
Описание¶
<Suspense>
¶
Свойства¶
children
: Фактический пользовательский интерфейс, который вы собираетесь рендерить. Еслиchildren
приостановится во время рендеринга, граница Suspense переключится на рендерингfallback
.fallback
: Альтернативный пользовательский интерфейс, который будет отображаться вместо реального пользовательского интерфейса, если он не закончил загрузку. Принимается любой допустимый узел React, хотя на практике запасной вариант - это легковесное представление-заполнитель, например, загрузочный спиннер или скелет. Приостановка будет автоматически переключаться наfallback
, когдаchildren
приостанавливает работу, и обратно наchildren
, когда данные будут готовы. Еслиfallback
приостанавливает работу во время рендеринга, он активирует ближайшую родительскую границу Suspense.
Ограничения¶
- React не сохраняет состояние для рендеров, которые были приостановлены до того, как они смогли смонтироваться в первый раз. Когда компонент загрузится, React повторит попытку рендеринга приостановленного дерева с нуля.
- Если
Suspense
отображал содержимое для дерева, но затем снова приостановился, тооткат
будет показан снова, если только обновление, вызвавшее его, не было вызваноstartTransition
илиuseDeferredValue
. - Если React необходимо скрыть уже видимый контент из-за повторного приостановления, он очистит layout Effects в дереве контента. Когда контент снова будет готов к показу, React снова запустит Эффекты компоновки. Это гарантирует, что Эффекты, измеряющие макет DOM, не попытаются сделать это, пока содержимое скрыто.
- React включает в себя такие "подкапотные" оптимизации, как Streaming Server Rendering и Selective Hydration, которые интегрированы в
Suspense
. Чтобы узнать больше, прочитайте архитектурный обзор и посмотрите технический доклад.
Использование¶
Отображение фолбэка во время загрузки контента¶
Вы можете обернуть любую часть вашего приложения границей Suspense
:
1 2 3 |
|
React будет отображать ваш loading fallback до тех пор, пока весь код и данные, необходимые потомкам не будут загружены.
В приведенном ниже примере компонент Albums
приостанавливается на время получения списка альбомов. Пока он не готов к рендерингу, React переключает ближайшую границу Suspense выше, чтобы показать отступающий компонент - ваш компонент Loading
. Затем, когда данные загружаются, React скрывает компонент Loading
и отображает компонент Albums
с данными.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Поддержка Suspense
Только источники данных с поддержкой Suspense активируют компонент Suspense. К ним относятся:
- Получение данных с помощью фреймворков с поддержкой Suspense, таких как Relay и Next.js.
- Ленивая загрузка кода компонента с помощью
lazy
. - Считывание значения промиса с использованием
use
Suspense не обнаруживает, когда данные извлекаются внутри Effect или обработчика события.
Точный способ загрузки данных в компонент Albums
, описанный выше, зависит от вашего фреймворка. Если вы используете фреймворк с поддержкой Suspense, вы найдете подробности в документации по получению данных.
Получение данных с поддержкой Suspense без использования мнений фреймворка пока не поддерживается. Требования к реализации источника данных с поддержкой Suspense нестабильны и не документированы. Официальный API для интеграции источников данных с Suspense будет выпущен в одной из будущих версий React.
Раскрытие содержимого сразу¶
По умолчанию все дерево внутри Suspense рассматривается как единое целое. Например, даже если только один из этих компонентов приостановится в ожидании каких-то данных, все они вместе будут заменены индикатором загрузки:
1 2 3 4 5 6 |
|
Затем, когда все они будут готовы к отображению, они появятся все вместе одновременно.
В приведенном ниже примере и Biography
, и Albums
получают некоторые данные. Однако, поскольку они сгруппированы под одной границей Suspense, эти компоненты всегда "всплывают" вместе в одно и то же время.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
1 2 3 |
|
Компоненты, загружающие данные, не обязательно должны быть прямыми дочерними компонентами границы Suspense. Например, вы можете переместить Biography
и Albums
в новый компонент Details
. Это не изменит поведение. Biography
и Albums
имеют одну и ту же ближайшую родительскую границу Suspense, поэтому их раскрытие координируется вместе.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Раскрытие вложенного содержимого по мере загрузки¶
Когда компонент приостанавливается, ближайший родительский Suspense-компонент показывает запасной вариант. Это позволяет вложить несколько компонентов Suspense для создания последовательности загрузки. Падение каждой границы Suspense будет заполняться по мере того, как становится доступным содержимое следующего уровня. Например, вы можете дать списку альбомов свой собственный откат:
1 2 3 4 5 6 7 8 |
|
С этим изменением отображение Biography
не должно "ждать" загрузки Albums
.
Последовательность будет следующей:
- Если
Biography
еще не загрузилась,BigSpinner
отображается вместо всей области содержимого. - Как только
Biography
завершает загрузку,BigSpinner
заменяется содержимым. - Если
Albums
еще не загрузились,AlbumsGlimmer
отображается вместоAlbums
и его родительскойPanel
. - Наконец, когда
Albums
завершает загрузку, он заменяетAlbumsGlimmer
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
1 2 3 |
|
Приостановочные границы позволяют вам координировать, какие части пользовательского интерфейса должны всегда "всплывать" одновременно, а какие - постепенно раскрывать больше содержимого в последовательности состояний загрузки. Вы можете добавлять, перемещать или удалять Suspense-границы в любом месте дерева, не влияя на поведение остального приложения.
Не ставьте приостанавливающую границу вокруг каждого компонента. Границы приостановки не должны быть более детализированными, чем последовательность загрузки, которую вы хотите, чтобы испытал пользователь. Если вы работаете с дизайнером, спросите его, где должны располагаться состояния загрузки - скорее всего, они уже включили их в свои эскизы.
Показ устаревшего контента во время загрузки свежего¶
В этом примере компонент SearchResults
приостанавливается на время получения результатов поиска. Введите "a"
, дождитесь результатов, а затем измените его на "ab"
. Результаты для "a"
будут заменены загрузочным фалбэком.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Распространенным альтернативным шаблоном пользовательского интерфейса является отложенное обновление списка и отображение предыдущих результатов до тех пор, пока не будут готовы новые результаты. Хук useDeferredValue
позволяет вам передать отложенную версию запроса вниз:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Запрос query
будет обновлен немедленно, поэтому на входе будет отображаться новое значение. Однако deferredQuery
сохранит свое предыдущее значение до тех пор, пока данные не загрузятся, поэтому SearchResults
будет отображать устаревшие результаты некоторое время.
Чтобы сделать это более очевидным для пользователя, вы можете добавить визуальную индикацию, когда отображается список несвежих результатов:
1 2 3 4 5 6 7 |
|
Введите "a"
в примере ниже, дождитесь загрузки результатов, а затем измените ввод на "ab"
. Обратите внимание, что вместо отката на приостановку вы теперь видите затемненный список несвежих результатов, пока не загрузятся новые результаты:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
И отложенные значения, и transitions позволяют вам избежать отображения Suspense fallback в пользу встроенных индикаторов. Переходы помечают все обновление как несрочное, поэтому они обычно используются фреймворками и библиотеками маршрутизаторов для навигации. Отложенные значения, с другой стороны, в основном полезны в коде приложений, где вы хотите пометить часть пользовательского интерфейса как несрочную и позволить ей "отстать" от остальной части пользовательского интерфейса.
Предотвращение скрытия уже раскрытого содержимого¶
Когда компонент приостанавливается, ближайшая родительская граница приостановки переключается на отображение резервного копирования. Это может привести к искажению пользовательского опыта, если уже отображалось какое-то содержимое. Попробуйте нажать эту кнопку:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
1 2 3 4 5 6 7 8 9 10 |
|
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
При нажатии кнопки компонент Router
отображал ArtistPage
вместо IndexPage
. Компонент внутри ArtistPage
приостанавливался, поэтому ближайшая граница Suspense начинала показывать откат. Ближайшая Suspense-граница находилась рядом с корнем, поэтому весь макет сайта заменялся на BigSpinner
.
Чтобы предотвратить это, вы можете пометить обновление состояния навигации как переход с помощью startTransition
:
1 2 3 4 5 6 7 8 9 10 |
|
Это говорит React, что переход состояния не является срочным, и лучше продолжать показывать предыдущую страницу вместо того, чтобы скрывать уже открытое содержимое. Теперь нажатие на кнопку "ждет" загрузки Биографии
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
1 2 3 4 5 6 7 8 9 10 |
|
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Переход не ждет, пока загрузится все содержимое. Он ждет только достаточно долго, чтобы не скрыть уже открытое содержимое. Например, сайт Layout
уже был показан, поэтому было бы плохо скрывать его за загружающимся волчком. Однако вложенная граница Suspense
вокруг Albums
является новой, поэтому переход не ждет ее.
Ожидается, что маршрутизаторы с поддержкой Suspense по умолчанию будут оборачивать обновления навигации в переходы.
Индикация того, что переход происходит¶
В приведенном выше примере после нажатия на кнопку нет визуальной индикации того, что происходит переход. Чтобы добавить индикатор, вы можете заменить startTransition
на useTransition
, что даст вам булево значение isPending
. В примере ниже это используется для изменения стиля заголовка сайта во время перехода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Сброс границ приостановки при навигации¶
Во время перехода React будет избегать скрытия уже показанного содержимого. Однако, если вы переходите на маршрут с другими параметрами, вы можете захотеть сказать React, что это другой контент. Вы можете выразить это с помощью key
:
1 |
|
Представьте, что вы перемещаетесь по странице профиля пользователя, и что-то приостанавливается. Если это обновление завернуто в переход, оно не вызовет откат для уже видимого содержимого. Это ожидаемое поведение.
Однако теперь представьте, что вы перемещаетесь между двумя разными профилями пользователей. В этом случае имеет смысл показать откат. Например, временная шкала одного пользователя представляет собой различное содержимое, чем временная шкала другого пользователя. Указывая ключ
, вы гарантируете, что React рассматривает профили разных пользователей как разные компоненты и сбрасывает границы Suspense во время навигации. Маршрутизаторы, интегрированные в Suspense, должны делать это автоматически.
Предоставление обратного хода для ошибок сервера и контента только для сервера¶
Если вы используете один из API потокового серверного рендеринга (или фреймворк, который полагается на них), React также будет использовать ваши границы <Suspense>
для обработки ошибок на сервере. Если компонент выдает ошибку на сервере, React не будет прерывать серверный рендеринг. Вместо этого он найдет ближайший компонент <Suspense>
над ним и включит его фалбэк (например, спиннер) в сгенерированный серверный HTML. Пользователь сначала увидит спиннер.
На клиенте React попытается отрисовать тот же компонент еще раз. Если и на клиенте произойдет ошибка, React выдаст ошибку и отобразит ближайшую границу ошибки. Однако, если ошибка не произойдет на клиенте, React не будет отображать ошибку пользователю, так как содержимое в итоге было отображено успешно.
Вы можете использовать это, чтобы исключить некоторые компоненты из рендеринга на сервере. Для этого бросьте ошибку в серверное окружение, а затем оберните их в границу <Suspense>
, чтобы заменить их HTML фалбэками:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
HTML сервера будет включать индикатор загрузки. На клиенте он будет заменен компонентом Chat
.
Устранение неполадок¶
Как предотвратить замену пользовательского интерфейса на fallback во время обновления?¶
Замена видимого пользовательского интерфейса на фалбэк приводит к резким изменениям в работе пользователя. Это может произойти, когда обновление приводит к приостановке компонента, а ближайшая граница приостановки уже показывает пользователю содержимое.
Чтобы этого не произошло, пометьте обновление как несрочное с помощью startTransition
. Во время перехода React будет ждать, пока загрузится достаточно данных, чтобы предотвратить появление нежелательного отката:
1 2 3 4 5 6 |
|
Это позволит избежать скрытия существующего содержимого. Тем не менее, все новые границы Suspense
будут немедленно отображать отступления, чтобы избежать блокировки пользовательского интерфейса и позволить пользователю видеть содержимое по мере его появления.
React будет предотвращать нежелательные отступления только во время несрочных обновлений. Он не будет задерживать рендеринг, если он является результатом срочного обновления. Вы должны выбрать API, например startTransition
или useDeferredValue
.
Если ваш маршрутизатор интегрирован с Suspense, он должен автоматически обернуть свои обновления в startTransition
.
Источник — https://react.dev/reference/react/Suspense