renderToReadableStream¶
renderToReadableStream
рендерит дерево React в Readable Web Stream.
1 |
|
Этот API зависит от Web Streams. Для Node.js используйте renderToPipeableStream
вместо этого.
Описание¶
renderToReadableStream(reactNode, options?)
¶
Вызовите renderToReadableStream
для рендеринга вашего дерева React в формате HTML в читаемый веб-поток.
1 2 3 4 5 6 7 8 9 10 |
|
На клиенте вызовите hydrateRoot
, чтобы сделать сгенерированный сервером HTML интерактивным.
Параметры
reactNode
: Узел React, который вы хотите отобразить в HTML. Например, JSX-элемент типа<App />
. Ожидается, что он будет представлять весь документ, поэтому компонентApp
должен вывести тег<html>
.- опционально
options
: Объект с опциями потоковой передачи.- опционально
bootstrapScriptContent
: Если указано, то эта строка будет помещена в инлайн тег<script>
. - опционально
bootstrapScripts
: Массив URL строк для тегов<script>
, которые будут отображаться на странице. Используйте его для включения<script>
, вызывающегоhydrateRoot
Опустите его, если вы вообще не хотите запускать React на клиенте. - опционально
bootstrapModules
: АналогичноbootstrapScripts
, но вместо него выдается<script type="module">
. - опционально
identifierPrefix
: Строковый префикс, который React использует для идентификаторов, генерируемыхuseId
Полезно для предотвращения конфликтов при использовании нескольких корней на одной странице. Должен быть тем же префиксом, что передан вhydrateRoot
- опционально
namespaceURI
: Строка с корневым namespace URI для потока. По умолчанию используется обычный HTML. Передайте'http://www.w3.org/2000/svg'
для SVG или'http://www.w3.org/1998/Math/MathML'
для MathML. - опционально
nonce
: Строкаnonce
для разрешения скриптов дляscript-src
Content-Security-Policy. - опционально
onError
: Обратный вызов, который срабатывает всякий раз, когда происходит ошибка сервера, будь то восстанавливаемая или нет По умолчанию, он вызывает толькоconsole.error
. Если вы переопределите его для регистрации сообщений о сбоях, убедитесь, что вы все еще вызываетеconsole.error
. Вы также можете использовать его для настройки кода состояния перед выдачей оболочки. - опционально
progressiveChunkSize
: Количество байт в чанке. Подробнее об эвристике по умолчанию. - опционально
signal
: Сигнал прерывания, который позволяет вам прервать серверный рендеринг и отрисовать все остальное на клиенте.
- опционально
Возвращает
renderToReadableStream
возвращает промис:
- Если рендеринг оболочки прошел успешно, этот промис будет разрешен в Readable Web Stream.
- Если рендеринг оболочки не удался, промис будет отклонен. Используйте это для вывода резервной оболочки.
Возвращаемый поток имеет дополнительное свойство:
allReady
: Обещание, которое разрешается, когда весь рендеринг завершен, включая оболочку и весь дополнительный контент. Вы можетедождаться stream.allReady
перед возвращением ответа для краулеров и статической генерации. Если вы сделаете это, вы не получите никакой прогрессивной загрузки. Поток будет содержать конечный HTML.
Использование¶
Рендеринг дерева React в формате HTML в читаемый веб-поток¶
Вызовите renderToReadableStream
для рендеринга вашего дерева React в формате HTML в Readable Web Stream:
1 2 3 4 5 6 7 8 9 10 |
|
Наряду с корневым компонентом, вам необходимо предоставить список путей к <script>
бутстрапа. Ваш корневой компонент должен возвращать весь документ, включая корневой тег <html>
..
Например, это может выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
React внедрит doctype и ваши теги bootstrap <script>
в результирующий поток HTML:
1 2 3 4 5 |
|
На клиенте ваш скрипт bootstrap должен гидратировать весь документ
с помощью вызова hydrateRoot
:
1 2 3 4 |
|
Это позволит прикрепить слушателей событий к генерируемому сервером HTML и сделать его интерактивным.
Чтение путей CSS и JS активов из выходных данных сборки
URL конечных активов (например, файлов JavaScript и CSS) часто хэшируются после сборки. Например, вместо styles.css
вы можете получить styles.123456.css
. Хеширование имен файлов статических активов гарантирует, что каждая отдельная сборка одного и того же актива будет иметь другое имя файла. Это полезно, поскольку позволяет безопасно включить долгосрочное кэширование для статических активов: файл с определенным именем никогда не изменит содержимое.
Однако, если вы не знаете URL-адреса активов до окончания сборки, у вас нет возможности поместить их в исходный код. Например, жесткое кодирование "/styles.css"
в JSX, как это было ранее, не сработает. Чтобы они не попадали в исходный код, ваш корневой компонент может считывать реальные имена файлов из карты, передаваемой в качестве пропса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
На сервере выполните рендеринг <App assetMap={assetMap} />
и передайте вашему assetMap
URLs активов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Поскольку ваш сервер теперь рендерит <App assetMap={assetMap} />
, вам нужно отобразить его с assetMap
на клиенте, чтобы избежать ошибок гидратации. Вы можете сериализовать и передать assetMap
клиенту следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
В примере выше опция bootstrapScriptContent
добавляет дополнительный встроенный тег <script>
, который устанавливает глобальную переменную window.assetMap
на клиенте. Это позволяет клиентскому коду читать ту же самую assetMap
:
1 2 3 4 |
|
И клиент, и сервер рендерят App
с одним и тем же пропсом assetMap
, поэтому ошибок гидратации нет.
Потоковая передача контента по мере его загрузки¶
Потоковая передача позволяет пользователю начать видеть содержимое еще до того, как все данные загрузятся на сервер. Например, рассмотрим страницу профиля, на которой отображается обложка, боковая панель с друзьями и фотографиями, а также список сообщений:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Представьте, что загрузка данных для <Posts />
занимает некоторое время. В идеале вы хотели бы показать пользователю остальную часть содержимого страницы профиля, не дожидаясь сообщений. Для этого оберните Posts
в границу <Suspense>
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Это указывает React начать передачу HTML до того, как Posts
загрузит свои данные. Сначала React отправит HTML для резервной загрузки (PostsGlimmer
), а затем, когда Posts
закончит загрузку своих данных, React отправит оставшийся HTML вместе со встроенным тегом <script>
, который заменит резервную загрузку этим HTML. С точки зрения пользователя, на странице сначала появится PostsGlimmer
, а затем его заменит Posts
.
Вы можете далее вложить границы <Suspense>
для создания более детальной последовательности загрузки:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
В этом примере React может начать потоковую передачу страницы даже раньше. Только ProfileLayout
и ProfileCover
должны закончить рендеринг первыми, потому что они не обернуты в какую-либо границу <Suspense>
. Однако, если Sidebar
, Friends
или Photos
потребуется загрузить некоторые данные, React отправит HTML для фалбэка BigSpinner
вместо них. Затем, по мере поступления данных, все большее количество содержимого будет продолжать отображаться, пока все не станет видимым.
Потоковая передача данных не требует ожидания загрузки самого React в браузере или интерактивности вашего приложения. HTML-содержимое с сервера будет постепенно раскрываться до загрузки любого из тегов <script>
.
Подробнее о том, как работает потоковый HTML.
Только источники данных с поддержкой Suspense активируют компонент Suspense. К ним относятся:
- Получение данных с помощью фреймворков с поддержкой Suspense, таких как Relay и Next.js.
- Ленивая загрузка кода компонента с помощью
lazy
.
Suspense не обнаруживает, когда данные извлекаются внутри Effect или обработчика события.
Точный способ загрузки данных в компонент Posts
, описанный выше, зависит от вашего фреймворка. Если вы используете фреймворк с поддержкой Suspense, вы найдете подробности в документации по получению данных.
Получение данных с поддержкой Suspense без использования фреймворка с поддержкой мнений пока не поддерживается. Требования к реализации источника данных с поддержкой Suspense нестабильны и не документированы. Официальный API для интеграции источников данных с Suspense будет выпущен в одной из будущих версий React.
Указание того, что входит в оболочку¶
Часть вашего приложения вне границ <Suspense>
называется оболочкой:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Он определяет самое раннее состояние загрузки, которое может увидеть пользователь:
1 2 3 4 |
|
Если вы обернете все приложение в границу <Suspense>
в корне, оболочка будет содержать только этот спиннер. Однако это не очень приятно для пользователя, потому что видеть большой спиннер на экране может показаться более медленным и раздражающим, чем подождать еще немного и увидеть реальный макет. Вот почему обычно вы хотите разместить границы <Suspense>
так, чтобы оболочка казалась минимальной, но полной - как скелет всего макета страницы.
Асинхронный вызов renderToReadableStream
разрешится в поток
, как только вся оболочка будет отрисована. Обычно вы начинаете потоковую передачу, создавая и возвращая ответ с этим потоком
:
1 2 3 4 5 6 7 8 |
|
К моменту возврата stream
, компоненты во вложенных границах <Suspense>
могут все еще загружать данные.
Протоколирование аварий на сервере¶
По умолчанию все ошибки на сервере записываются в консоль. Вы можете переопределить это поведение для записи отчетов о сбоях:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Если вы предоставляете пользовательскую реализацию onError
, не забудьте также вести журнал ошибок в консоль, как описано выше.
Восстановление после ошибок внутри оболочки¶
В этом примере оболочка содержит ProfileLayout
, ProfileCover
и PostsGlimmer
:
1 2 3 4 5 6 7 8 9 10 |
|
Если при рендеринге этих компонентов произойдет ошибка, у React не будет никакого осмысленного HTML для отправки клиенту. Оберните вызов renderToReadableStream
в try...catch
для отправки резервного HTML, который не полагается на серверный рендеринг в качестве последнего средства:
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 |
|
Если при генерации оболочки произошла ошибка, сработает и onError
, и ваш блок catch
. Используйте onError
для сообщения об ошибке и используйте блок catch
для отправки резервного HTML-документа. Ваш резервный HTML не обязательно должен быть страницей ошибки. Вместо этого вы можете включить альтернативную оболочку, которая отображает ваше приложение только на клиенте.
Восстановление после ошибок вне оболочки¶
В этом примере компонент <Posts />
обернут в <Suspense>
, поэтому он не является частью оболочки:
1 2 3 4 5 6 7 8 9 10 |
|
Если ошибка произойдет в компоненте Posts
или где-то внутри него, React попытается восстановиться после нее:
- Выдаст в HTML фаллбэк загрузки для ближайшей границы
<Suspense>
(PostsGlimmer
). - Он "откажется" от попыток рендеринга содержимого
Posts
на сервере. - Когда код JavaScript загрузится на клиенте, React повторит рендеринг
Posts
на клиенте.
Если повторная попытка рендеринга Posts
на клиенте также не удалась, React выбросит ошибку на клиент. Как и для всех ошибок, возникающих во время рендеринга, ближайшая родительская граница ошибки определяет, как представить ошибку пользователю. На практике это означает, что пользователь будет видеть индикатор загрузки до тех пор, пока не будет уверен, что ошибка не может быть устранена.
Если повторная попытка рендеринга Posts
на клиенте будет успешной, индикатор загрузки с сервера будет заменен на вывод клиентского рендеринга. Пользователь не будет знать, что произошла ошибка сервера. Однако обратный вызов сервера onError
и обратный вызов клиента onRecoverableError
сработают, чтобы вы могли получить уведомление об ошибке.
Установка кода состояния¶
При потоковой передаче возникает компромисс. Вы хотите начать потоковую передачу страницы как можно раньше, чтобы пользователь мог быстрее увидеть содержимое. Однако после начала потоковой передачи вы больше не сможете установить код состояния ответа.
Разделив приложение на оболочку (выше всех границ <Suspense>
) и остальное содержимое, вы уже решили часть этой проблемы. Если оболочка ошибется, будет запущен ваш блок catch
, который позволит вам установить код состояния ошибки. В противном случае вы знаете, что приложение может восстановиться на клиенте, поэтому вы можете послать "OK".
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 |
|
Если компонент за пределами оболочки (т.е. внутри границы <Suspense>
) выдаст ошибку, React не остановит рендеринг. Это означает, что обратный вызов onError
сработает, но ваш код продолжит выполняться, не попадая в блок catch
. Это происходит потому, что 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 |
|
Это позволит отловить только те ошибки вне оболочки, которые возникли при генерации начального содержимого оболочки, так что этот способ не является исчерпывающим. Если знать, произошла ли ошибка для какого-то содержимого, очень важно, вы можете перенести ее в оболочку.
Обработка различных ошибок различными способами¶
Вы можете создавать собственные подклассы Error
и использовать оператор instanceof
для проверки того, какая ошибка была выброшена. Например, вы можете определить пользовательскую NotFoundError
и выбросить ее из вашего компонента. Затем вы можете сохранить ошибку в onError
и сделать что-то другое перед возвратом ответа в зависимости от типа ошибки:
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 |
|
Имейте в виду, что после эмиссии оболочки и начала потоковой передачи вы не сможете изменить код состояния.
Ожидание загрузки всего содержимого для краулеров и статической генерации¶
Потоковая передача обеспечивает лучший пользовательский опыт, поскольку пользователь может видеть содержимое по мере его появления.
Однако, когда краулер посещает вашу страницу, или если вы генерируете страницы во время сборки, вы можете захотеть сначала позволить всему содержимому загрузиться, а затем создать окончательный HTML-вывод вместо того, чтобы раскрывать его постепенно.
Вы можете дождаться загрузки всего содержимого, ожидая выполнения промиса stream.allReady
:
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 |
|
Обычный посетитель получит поток постепенно загружаемого содержимого. Краулер получит окончательный HTML-вывод после загрузки всех данных. Однако это также означает, что краулеру придется ждать все данные, некоторые из которых могут загружаться медленно или с ошибками. В зависимости от вашего приложения, вы можете выбрать вариант отправки оболочки краулерам.
Прерывание рендеринга сервера¶
Вы можете заставить серверный рендеринг "сдаться" после таймаута:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
React пропустит оставшиеся фаллаверы загрузки как HTML, а остальные попытается отобразить на клиенте.
Источник — https://react.dev/reference/react-dom/server/renderToReadableStream