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¶
Граница Suspense ждёт, пока содержимое будет готово, и только потом показывает его. Граница не раскрывает содержимое, пока происходит что-то из этого списка:
- Ленивая загрузка кода компонента через
lazy. - Чтение промиса через
use, включая данные, которые приходят потоком из серверных компонентов или загружаются через фреймворк с поддержкой Suspense. - Загрузка таблицы стилей, отрисованной через
<link rel="stylesheet">с пропомprecedence. React удерживает границу, пока таблица стилей не загрузится, но не дольше тайм-аута. Пример ниже. - Ожидание HTML большой границы при потоковом серверном рендеринге. Передача HTML занимает время, поэтому граница с достаточным объёмом содержимого активируется, даже если внутри ничего не приостанавливается. React показывает содержимое по мере поступления HTML.
- Загрузка шрифтов. По умолчанию Suspense не ждёт шрифты, но обновление
<ViewTransition>ждёт загрузки новых шрифтов до тайм-аута, чтобы текст не мигал запасным шрифтом. Пример ниже. - Загрузка изображений. По умолчанию Suspense не ждёт изображения, но во время обновления
<ViewTransition>React удерживает границу, пока изображение не загрузится, до тайм-аута. ОбработчикonLoadисключает конкретное изображение из этого ожидания. Пример ниже. - Вычисления, которые занимают процессор, внутри границы
<Suspense defer>.
Фреймворки с поддержкой Suspense
Фреймворк с поддержкой Suspense даёт способ читать данные в компоненте так, чтобы активировалась ближайшая граница Suspense. Точный способ загрузки зависит от фреймворка и описан в его документации. Под капотом такой фреймворк хранит кэш промисов и вызывает use, чтобы приостановиться на промисе.
Без фреймворка промис можно читать через use напрямую, если промис закэширован и один и тот же экземпляр переиспользуется между рендерами.
Использование¶
Отображение фолбэка во время загрузки контента¶
Вы можете обернуть любую часть вашего приложения границей 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.
Фолбэк для содержимого только в браузере¶
Граница приостановки может показать фолбэк для компонента, который нужен только в браузере. Оберните компонент в <Suspense> и вызовите внутри use(browser()).
Нажмите Reload, чтобы увидеть фолбэк загрузки в исходном HTML. После гидратации React показывает черновик, загруженный из localStorage.
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 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
1 2 3 4 5 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Во время серверного рендеринга React включает фолбэк границы приостановки в HTML. В браузере React заменяет фолбэк сохранённым черновиком.
Ожидание загрузки таблицы стилей¶
Таблица стилей, отрендеренная через <link rel="stylesheet"> и проп precedence, блокирует границу приостановки, пока таблица стилей не загрузится, но не дольше таймаута, чтобы содержимое не появилось без стилей.
В примере ниже компонент Card рендерит таблицу стилей с precedence. Нажмите «Show card»: React показывает фолбэк, пока таблица стилей не загрузится, а затем раскрывает карточку уже со стилями.
Для сравнения вторая кнопка выполняет то же обновление без 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 | |
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 | |
1 2 3 4 5 6 7 8 9 | |
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 | |
Анимация от содержимого Suspense¶
Suspense сочетается с <ViewTransition>, чтобы анимировать смену фолбэка на содержимое. Оберните границу в <ViewTransition>, и 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 | |
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 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | |
1 2 3 4 5 6 7 | |
Примечание
От того, где <ViewTransition> стоит относительно границы, зависит, будет ли фолбэк и содержимое перекрёстно затухать как одно обновление или анимироваться как отдельные анимации выхода и входа. Анимацию также можно настроить классами View Transition.
Ожидание загрузки шрифта¶
Когда <ViewTransition> анимирует раскрытие границы приостановки, React ждёт новые шрифты, которые вводит содержимое, но не дольше таймаута, чтобы текст не мигал запасным шрифтом. Это происходит только во время обновления <ViewTransition>.
В примере ниже граница приостановки обёрнута в <ViewTransition>, а компонент Quote приостанавливается, пока загружаются его данные. Рендер цитаты запускает загрузку шрифта. React держит фолбэк видимым, пока шрифт не загрузится, поэтому цитата появляется уже своим шрифтом.
Для сравнения вторая кнопка выполняет то же обновление без 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 42 43 44 45 | |
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 4 5 6 7 8 9 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
1 2 3 4 5 6 7 | |
Ожидание загрузки изображения¶
Когда <ViewTransition> анимирует раскрытие границы приостановки, React ждёт загрузки видимых изображений, но не дольше таймаута, чтобы анимация не началась с наполовину загруженной картинки. Это происходит только во время обновления <ViewTransition>. Обработчик onLoad исключает конкретное изображение из ожидания, даже внутри <ViewTransition>.
В примере ниже граница приостановки обёрнута в <ViewTransition> и показывает скелет профиля, пока не загрузится портрет.
Для сравнения вторая кнопка выполняет то же обновление без 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 42 43 44 45 46 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 2 3 4 5 | |
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 | |
1 2 3 4 5 6 7 | |
Согласование шрифтов, изображений и таблиц стилей¶
Граница приостановки может одновременно ждать данные, таблицы стилей, шрифты и изображения. Ожидание шрифтов и изображений бывает только во время обновления <ViewTransition>. В примере ниже компонент ProfileCard приостанавливается, пока загружаются его данные, и рендерит таблицу стилей с precedence, текст новым шрифтом и портрет. React держит скелет видимым, пока загружаются данные и таблица стилей. Затем раскрытие <ViewTransition> ждёт шрифт и изображение, чтобы карточка появилась целиком.
Для сравнения версия без 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | |
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 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
1 2 3 4 5 6 7 8 9 10 | |
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
1 2 3 4 5 6 7 | |
Устранение неполадок¶
Как предотвратить замену пользовательского интерфейса на fallback во время обновления?¶
Замена видимого пользовательского интерфейса на фалбэк приводит к резким изменениям в работе пользователя. Это может произойти, когда обновление приводит к приостановке компонента, а ближайшая граница приостановки уже показывает пользователю содержимое.
Чтобы этого не произошло, пометьте обновление как несрочное с помощью startTransition. Во время перехода React будет ждать, пока загрузится достаточно данных, чтобы предотвратить появление нежелательного отката:
1 2 3 4 5 6 | |
Это позволит избежать скрытия существующего содержимого. Тем не менее, все новые границы Suspense будут немедленно отображать отступления, чтобы избежать блокировки пользовательского интерфейса и позволить пользователю видеть содержимое по мере его появления.
React будет предотвращать нежелательные отступления только во время несрочных обновлений. Он не будет задерживать рендеринг, если он является результатом срочного обновления. Вы должны выбрать API, например startTransition или useDeferredValue.
Если ваш маршрутизатор интегрирован с Suspense, он должен автоматически обернуть свои обновления в startTransition.
Источник — https://react.dev/reference/react/Suspense