В React 18 обновится подход к SSR
Используя API <Suspense> можно отделить «тяжелые» участки вашего приложения и ускорить отдачу серверного кода в клиент. Возможно это, если использовать новое API pipeToNodeWritable.
Но обо всем по порядку. Что такое SSR?
При загрузке сайта без SSR, пользователь сначала видит белую страницу, и только после загрузки бандла с JS начнет отображаться UI, и пользователь может с ним работать.
Если есть SSR, то при загрузке сайта пользователь практически сразу видит UI, но пользователь никак не может с ним взаимодействовать: обработчики событий еще не подключены. Необходимо подождать загрузки JS бандлов, после чего React соберет в памяти дерево компонентов и подключит к существующему HTML логику приложения. Это называется гидратация.
Однозначным плюсом SSR является то, что пользователи с более медленным интернетом смогут быстрее увидеть UI, однако показатель TTI (Time to Interactive) лучше не станет.
Какие минусы SSR в React 17?
- Необходимо дождаться загрузки данных. При получение страницы с сервера, необходимо чтобы сервер имел все данные для компонентов для их рендера. То есть серверу необходимо сходить в API, получить данные и передать их компоненту, после чего будет собран HTML и передан клиенту.
- Необходимо дождаться загрузки всех JS бандлов. Это означает, что если есть модуль, с сложной логикой внутри и большим количеством кода, то на клиенте необходимо дождаться загрузки бандла с кодом этого модуля. Это происходит из за того, что гидратация проходит в один этап, поэтому все бандлы с кодом должны быть загружены, иначе если для HTML кода нет связанного с ним загруженного кода из бандла, то он будет удален из HTML во время гидратации.
- Для взаимодействия с UI необходимо дождаться окончания гидратации. Этот минус связан с особенностью гидратации, которая проходит дерево один раз. Это значит, что если есть какой компонент, со сложной логикой рендера, то это может стать узким горлышком на этапе гидратации. Пользователь не сможет взаимодействовать с UI, пока гидратация не будет закончена полностью.
Используя API <Suspense> можно отделить «тяжелые» участки вашего приложения и ускорить отдачу серверного кода в клиент. Возможно это, если использовать новое API pipeToNodeWritable.
Но обо всем по порядку. Что такое SSR?
При загрузке сайта без SSR, пользователь сначала видит белую страницу, и только после загрузки бандла с JS начнет отображаться UI, и пользователь может с ним работать.
Если есть SSR, то при загрузке сайта пользователь практически сразу видит UI, но пользователь никак не может с ним взаимодействовать: обработчики событий еще не подключены. Необходимо подождать загрузки JS бандлов, после чего React соберет в памяти дерево компонентов и подключит к существующему HTML логику приложения. Это называется гидратация.
Однозначным плюсом SSR является то, что пользователи с более медленным интернетом смогут быстрее увидеть UI, однако показатель TTI (Time to Interactive) лучше не станет.
Какие минусы SSR в React 17?
- Необходимо дождаться загрузки данных. При получение страницы с сервера, необходимо чтобы сервер имел все данные для компонентов для их рендера. То есть серверу необходимо сходить в API, получить данные и передать их компоненту, после чего будет собран HTML и передан клиенту.
- Необходимо дождаться загрузки всех JS бандлов. Это означает, что если есть модуль, с сложной логикой внутри и большим количеством кода, то на клиенте необходимо дождаться загрузки бандла с кодом этого модуля. Это происходит из за того, что гидратация проходит в один этап, поэтому все бандлы с кодом должны быть загружены, иначе если для HTML кода нет связанного с ним загруженного кода из бандла, то он будет удален из HTML во время гидратации.
- Для взаимодействия с UI необходимо дождаться окончания гидратации. Этот минус связан с особенностью гидратации, которая проходит дерево один раз. Это значит, что если есть какой компонент, со сложной логикой рендера, то это может стать узким горлышком на этапе гидратации. Пользователь не сможет взаимодействовать с UI, пока гидратация не будет закончена полностью.
👍2
Как можно исправить существующие минусы?
Рассмотрим текущий флоу SSR: запрос данных (сервер) -> рендер HTML (сервер) -> загрузка кода (клиент) -> гидратация (клиент). Флоу выполняется по шагам, ожидая завершения предыдущего, что не очень эффективно. Этот флоу выполняется для всего приложения, т.е. если некоторые его компоненты «тормозят» на одном из шагов, то тормозит весь флоу. Решением этой проблемы является разбитие флоу приложения на несколько частей, по компонентам, вместо целого приложения. Кстати, эта идея не нова, и уже использовалась в фреймворке Marko для реализации данного паттерна.
Для разбития приложения на части используется <Suspense>. Вот что можно делать с его помощью:
- Потоковая отправка HTML. Позволяет не ждать загрузки всех данных на сервере для рендера, отправить HTML клиенту, а как только данные будут получены, отрендерить на сервере, и отправить на клиент. Например, есть блок с комментариями, и мы можем асинхронно загружать по ним информацию и отдавать HTML, а когда комментарии будут получены, отрендерить их и отправить клиенту в конце HTML документа, выглядеть это будет примерно так:
- Выборочная гидратация на клиенте. Если у вас есть компоненты со сложной логикой, то обернув их в Suspense, React при гидратации компонентов не будет блокировать UI. Гидратация будет происходить во время простоя браузера, поэтому пользовательские события будут обработаны сразу. При этом, если на клиенте сразу несколько разных компонентов ожидают гидратации, то их гидратация будет происходить с учетом пользовательских событий. Например, есть блок комментариев, который находится на очереди гидратации, и если пользователь сделал клик в пределах этого компонента, то React запомнит этот клик и сделает гидратацию комментариев в первую очередь, после чего «воспроизведет» клик заново (dispatch события).
Более подробно, с иллюстрациями, в теме на Github.
Рассмотрим текущий флоу SSR: запрос данных (сервер) -> рендер HTML (сервер) -> загрузка кода (клиент) -> гидратация (клиент). Флоу выполняется по шагам, ожидая завершения предыдущего, что не очень эффективно. Этот флоу выполняется для всего приложения, т.е. если некоторые его компоненты «тормозят» на одном из шагов, то тормозит весь флоу. Решением этой проблемы является разбитие флоу приложения на несколько частей, по компонентам, вместо целого приложения. Кстати, эта идея не нова, и уже использовалась в фреймворке Marko для реализации данного паттерна.
Для разбития приложения на части используется <Suspense>. Вот что можно делать с его помощью:
- Потоковая отправка HTML. Позволяет не ждать загрузки всех данных на сервере для рендера, отправить HTML клиенту, а как только данные будут получены, отрендерить на сервере, и отправить на клиент. Например, есть блок с комментариями, и мы можем асинхронно загружать по ним информацию и отдавать HTML, а когда комментарии будут получены, отрендерить их и отправить клиенту в конце HTML документа, выглядеть это будет примерно так:
<div hidden id="comments">
<!-- Comments -->
<p>First comment</p>
<p>Second comment</p>
</div>
<script>
// Примерная реализация
document.getElementById('sections-spinner').replaceChildren(
document.getElementById('comments')
);
</script>
- Выборочная гидратация на клиенте. Если у вас есть компоненты со сложной логикой, то обернув их в Suspense, React при гидратации компонентов не будет блокировать UI. Гидратация будет происходить во время простоя браузера, поэтому пользовательские события будут обработаны сразу. При этом, если на клиенте сразу несколько разных компонентов ожидают гидратации, то их гидратация будет происходить с учетом пользовательских событий. Например, есть блок комментариев, который находится на очереди гидратации, и если пользователь сделал клик в пределах этого компонента, то React запомнит этот клик и сделает гидратацию комментариев в первую очередь, после чего «воспроизведет» клик заново (dispatch события).
Более подробно, с иллюстрациями, в теме на Github.
Изменения <Suspense> в React 18
Рассмотрим список изменений, которые будут в React 18.
Изменения в текущем поведение <Suspense>
- Вызов эффектов у компонентов внутри <Suspense> будет запускаться только после того, как компонент будет отображен пользователю. Раньше это происходило слишком рано, еще в моменте когда отображался плейсхолдер компонента.
- Повторный запуск эффекта useLayoutEffect при появление и исчезании компонента. Если компонент, который был отображен пользователю уходит в «ожидание», и исчезает из видимости, то внутри компонента никак нельзя было узнать об этом. Сейчас при появление компонента будет вызываться эффект useLayoutEffect, а при исчезание вызываться возвращаемая им функция (похож на componentWillUnmount).
- <Suspense> не будет вызывать ошибок на стороне сервера. Раньше нельзя было использовать <Suspense> на стороне сервера, теперь будет можно.
Новые фичи <Suspense>
- startTransition внутри <Suspense> предотвратит исчезание компонента, даже если он переходит в режим «ожидания». Это позволит легко реализовать паттерн «показа старых данных при загрузке новых».
- Встроенный тротлинг в <Suspense>. React будет сам тротлить показ вложенных фолбеков <Suspense>, поэтому UI не будет постоянно дергаться и показываться плейсхолдер при быстрых обновлениях данных.
- Использование <Suspense> и lazy компонентов на сервере. Вместе с pipeToNodeWritable позволяет использовать потоковый стриминг HTML и частичную гидратацию компонентов на клиенте.
<Suspense> и получение данных
Выше приведенные изменения являются фундаментальными архитектурными изменения в <Suspense>, однако они не решают проблему получения данных внутри Suspense. Скорее всего, после 18 версии выйдут минорные версии, которые добавят следующий функционал:
- Библиотека ввода/вывода типа react-fetch, которая предоставит простой способ получения данных внутри <Suspense>.
- Встроенный Suspense <Cache>, который будет основным рекомендованным способом интеграции с <Suspense> для сторонних библиотек получения данных (react-fetch использует его).
- Серверные компоненты будет рекомендованным способом для получения данных в <Suspense>, который хорошо масштабируется и интегрируется с react-fetch и другими сторонними библиотеками.
- чистая и понятная документация для сторонних библиотек получения данных для интеграции с Suspense.
https://github.com/reactwg/react-18/discussions/47
Рассмотрим список изменений, которые будут в React 18.
Изменения в текущем поведение <Suspense>
- Вызов эффектов у компонентов внутри <Suspense> будет запускаться только после того, как компонент будет отображен пользователю. Раньше это происходило слишком рано, еще в моменте когда отображался плейсхолдер компонента.
- Повторный запуск эффекта useLayoutEffect при появление и исчезании компонента. Если компонент, который был отображен пользователю уходит в «ожидание», и исчезает из видимости, то внутри компонента никак нельзя было узнать об этом. Сейчас при появление компонента будет вызываться эффект useLayoutEffect, а при исчезание вызываться возвращаемая им функция (похож на componentWillUnmount).
- <Suspense> не будет вызывать ошибок на стороне сервера. Раньше нельзя было использовать <Suspense> на стороне сервера, теперь будет можно.
Новые фичи <Suspense>
- startTransition внутри <Suspense> предотвратит исчезание компонента, даже если он переходит в режим «ожидания». Это позволит легко реализовать паттерн «показа старых данных при загрузке новых».
- Встроенный тротлинг в <Suspense>. React будет сам тротлить показ вложенных фолбеков <Suspense>, поэтому UI не будет постоянно дергаться и показываться плейсхолдер при быстрых обновлениях данных.
- Использование <Suspense> и lazy компонентов на сервере. Вместе с pipeToNodeWritable позволяет использовать потоковый стриминг HTML и частичную гидратацию компонентов на клиенте.
<Suspense> и получение данных
Выше приведенные изменения являются фундаментальными архитектурными изменения в <Suspense>, однако они не решают проблему получения данных внутри Suspense. Скорее всего, после 18 версии выйдут минорные версии, которые добавят следующий функционал:
- Библиотека ввода/вывода типа react-fetch, которая предоставит простой способ получения данных внутри <Suspense>.
- Встроенный Suspense <Cache>, который будет основным рекомендованным способом интеграции с <Suspense> для сторонних библиотек получения данных (react-fetch использует его).
- Серверные компоненты будет рекомендованным способом для получения данных в <Suspense>, который хорошо масштабируется и интегрируется с react-fetch и другими сторонними библиотеками.
- чистая и понятная документация для сторонних библиотек получения данных для интеграции с Suspense.
https://github.com/reactwg/react-18/discussions/47
GitHub
Behavioral changes to Suspense in React 18 · reactwg/react-18 · Discussion #7
Overview We added basic support for Suspense in React 16.x. But it wasn’t full support for Suspense — it doesn’t do all the things we’ve shown off in our demos, like delayed transitions (i.e. waiti...
