Svelte

Паттерны загрузки данных

Страница заказа тянет профиль пользователя, список заказов и рекомендации. Написанная в лоб, она делает три запроса один за другим, и пользователь ждёт сумму всех трёх задержек. Хуже того, рекомендации считаются на отдельном медленном сервисе и держат весь экран ради второстепенного блока. SvelteKit даёт инструменты на оба случая: независимые запросы идут параллельно, а медленные можно отдать стримингом - страница появляется сразу, а тяжёлый блок дорисовывается, когда будет готов. Плюс точечная инвалидация перезагружает ровно те данные, что устарели.

  • Страница заказа: профиль, заказы и рекомендации грузятся параллельно вместо суммы трёх задержек
  • Дашборд: быстрые метрики рендерятся сразу, тяжёлый отчёт стримится и появляется через секунду
  • Лента с фильтром: invalidate перезагружает только список по новому фильтру, не трогая шапку и сайдбар
  • После логина: invalidateAll обновляет все load, чтобы зависящие от пользователя блоки показали его данные
  • Layout + page: общий load в +layout берёт пользователя один раз, страница не запрашивает его повторно

Предварительные знания

  • Load-функции SvelteKit: +page.js, +page.server.js, проп data
  • Промисы, Promise.all и async/await в JavaScript
  • Блок {#await} в Svelte для отображения промиса в разметке

Водопады и параллельная загрузка

Водопад - это цепочка запросов, где каждый ждёт завершения предыдущего без необходимости. Если профиль, заказы и рекомендации не зависят друг от друга, но написаны как три последовательных await, общее время равно их сумме. Решение то же, что в базовом load: запускать независимые промисы сразу и дожидаться их вместе через Promise.all.

Есть и второй источник водопада - между уровнями маршрута. load в +layout.server.js и load в +page.server.js по умолчанию исполняются параллельно, а не друг за другом. Поэтому общие для раздела данные (пользователь, навигация) логично грузить один раз в layout, а страница берёт своё. Если же странице действительно нужен результат layout, его получают через await parent(), и только тогда возникает осознанная последовательность.

СитуацияПоведениеКогда уместно
Последовательные awaitВодопад, времена складываютсяТолько если второй запрос зависит от первого
Promise.allПараллель, время = самый медленныйНезависимые запросы внутри одного load
layout load + page loadИдут параллельно по умолчаниюОбщие данные в layout, частные на странице
await parent()Page ждёт данные layoutКогда странице реально нужен результат layout

Вызов await parent() без необходимости вновь создаёт водопад: страница начнёт грузить своё только после layout. Его применяют, лишь когда запрос страницы действительно зависит от данных родителя, а не по привычке.

load загружает три независимых ресурса тремя последовательными await. Как ускорить без потери данных?

depends, invalidate и invalidateAll

SvelteKit перезапускает load автоматически, когда меняются его наблюдаемые зависимости. Если load обратился к params.id, url.searchParams или fetch на некий URL, фреймворк запоминает это и перезапустит load при изменении соответствующей сущности. Но иногда нужно перезагрузить данные по событию, не связанному напрямую с url, например после мутации. Для этого есть управляемая инвалидация.

depends(id) регистрирует произвольный идентификатор как зависимость данного load. Вызов invalidate(id) перезапускает ровно те load, что объявили этот идентификатор, не трогая остальные - шапка и сайдбар не дёргаются. invalidate можно передать и URL: invalidate('/api/orders') перезапустит каждый load, который fetch-ил этот адрес. invalidateAll() перезапускает все активные load сразу.

ВызовЧто перезапускаетТипичный повод
invalidate('app:orders')load с depends('app:orders')После создания или отмены заказа
invalidate('/api/orders')load, дергавшие этот URLКогда зависимость завязана на конкретный fetch
invalidateAll()Все активные loadСмена пользователя, логин или логаут

Собственные идентификаторы зависимостей удобно именовать с префиксом, например app:orders или app:cart. Это отделяет их от URL-зависимостей и делает явным, что именно инвалидируется при вызове invalidate.

После создания заказа нужно обновить только список заказов, не перезагружая шапку и сайдбар. Как это сделать?

Стриминг медленных данных

Иногда часть данных критична для первого экрана, а часть - нет. Профиль и заказы нужны сразу, а рекомендации с медленного сервиса можно дорисовать позже. Если await-ить всё в load, страница будет ждать самый медленный запрос. SvelteKit умеет стримить: критичные данные await-ятся и попадают в HTML сразу, а медленный промис возвращается из load неразрешённым.

Ключевой приём: промис recommendations не await-ят в load, а возвращают как есть. SvelteKit отдаёт первый HTML с уже готовым user, а значение промиса досылает по тому же соединению, когда оно разрешится. В разметке блок {#await} показывает заглушку, затем результат или ошибку. Первый полезный экран появляется без ожидания медленного сервиса.

Стриминг требует платформы с поддержкой потоковых ответов. На статическом хостинге (полностью пререндеренном) его нет - там данные должны быть готовы на этапе сборки. Кроме того, у неразрешённого промиса важно обработать ошибку (ветка {:catch} или .catch), иначе непойманный reject обвалит ответ.

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

Связь с другими темами

Этот урок развивает базовый load в сторону скорости и управляемости:

  • Load-функции — Фундамент: где живёт load и как данные попадают на страницу. Здесь добавляется производительность
  • Гидрация — Стриминг и точечная гидрация служат одной цели - ранний полезный экран и быстрые Core Web Vitals
  • Hooks — locals и сессия определяют, какие load зависят от пользователя и что инвалидировать после входа

Итог

  • Водопад возникает, когда независимые запросы идут последовательными await; параллельный запуск через Promise.all устраняет его
  • Load в +layout исполняется отдельно от load страницы и параллельно ему; общие данные грузят один раз в layout
  • depends(id) объявляет произвольную зависимость load; invalidate(id) перезапускает именно те load, что от неё зависят
  • invalidateAll() перезапускает все активные load - удобно после смены пользователя; зависимость от url создаётся автоматически
  • Не дожидаясь промиса в load, его возвращают как есть; {#await} в разметке стримит результат, не блокируя первый рендер

Связанные уроки

  • sv-19-load-functions — Базовый load уже знаком; здесь те же функции выводятся на уровень производительности и управляемого обновления
  • sv-26-hydration — Стриминг промисов и точечная гидрация - две стороны идеи отдавать пользователю готовое как можно раньше
  • sv-22-hooks — Зависимости load часто завязаны на сессию из locals; инвалидация после логина обновляет данные, зависящие от пользователя
Паттерны загрузки данных

0

1

Войти