Перейти к содержимому

Поверхностная маршрутизация

Когда вы перемещаетесь по приложению SvelteKit, создаются записи истории. При нажатии кнопок «Назад» и «Вперёд» происходит перемещение по этому списку записей, при этом заново выполняются любые функции load и заменяются компоненты страницы по мере необходимости.

Иногда бывает полезно создавать записи истории без перехода на другую страницу. Например, вы можете захотеть показать модальное окно (диалог), которое пользователь сможет закрыть, нажав кнопку «Назад». Это особенно ценно на мобильных устройствах, где жесты смахивания (swipe) часто более естественны, чем прямое взаимодействие с интерфейсом. В таких случаях модальное окно, не связанное с записью истории, может вызывать раздражение: пользователь может смахнуть назад, пытаясь его закрыть, и в итоге окажется на неправильной странице.

SvelteKit делает это возможным с помощью функций pushState и replaceState. Они позволяют ассоциировать состояние с записью истории без перехода на другую страницу. Например, для реализации модального окна, управляемого через историю:

+page.svelte
<script>
import { pushState } from '$app/navigation';
import { page } from '$app/state';
import Modal from './Modal.svelte';
function showModal() {
pushState('', {
showModal: true
});
}
</script>
{#if page.state.showModal}
<Modal close={() => history.back()} />
{/if}

Состояние доступно глобально через объект page как page.state. Вы можете сделать состояние страницы типобезопасным, объявив интерфейс App.PageState (обычно в файле src/app.d.ts).

Модальное окно можно закрыть, перейдя назад (сбросив page.state.showModal) или взаимодействуя с ним таким образом, чтобы выполнился колбэк close, который программно выполнит переход назад.

Вы также можете обновить содержимое адресной строки браузера во время поверхностной навигации:

goto('/another/page', {
state,
shallow: true
});

Независимо от того, решите ли вы обновлять отображаемый URL, beforeNavigate, onNavigate и afterNavigate будут вызваны с navigation.type === 'goto' и navigation.shallow === true.

После активации поверхностной навигации page.shallow становится объектом { url, params, route }, описывающим страницу, которая была бы отображена, если бы пользователь перешёл на неё (например, при перезагрузке страницы). При этом page.url, page.params и page.route по-прежнему описывают страницу, которая отображается в настоящий момент.

+page.svelte
<script>
import { goto } from '$app/navigation';
import { page } from '$app/state';
</script>
<p>URL, отображаемый пользователю: {page.shallow?.url.href ?? page.url.href}</p>
<p>Фактическая страница, на которой вы находитесь: {page.url.href}</p>
<button onclick={() => goto('/shallow', { shallow: true })}>перейти на поверхностный маршрут</button>

Обычный вызов goto без shallow: true или стандартный переход по ссылке завершает режим поверхностной навигации.

При переходе назад или вперёд к записи с поверхностной навигацией восстанавливаются её page.state и page.shallow. При этом отображаемая страница, а следовательно и page.url, остаётся той, на которой пользователь находился в момент вызова goto. Чтобы перейти к отображаемому URL, вызовите goto(page.shallow.url) без параметра shallow: true.

По умолчанию приведённые выше примеры создают новую запись в истории навигации. Если это не требуется, можно вместо этого заменить текущую запись:

goto(url, {
state,
replace: true
});

По умолчанию состояние, установленное с помощью goto, не восстанавливается после перезагрузки страницы. Чтобы изменить это поведение, используйте параметр persistState:

goto(url, {
state,
persistState: true
});

По умолчанию поверхностная навигация сохраняет текущую позицию прокрутки и элемент, находящийся в фокусе. Вы можете отключить это поведение с помощью параметра reset: true:

goto(url, {
shallow: true,
reset: true
});

При поверхностной маршрутизации вам может понадобиться отрисовать другой компонент +page.svelte внутри текущей страницы. Например, при клике на миниатюру фото может открыться детальный просмотр без перехода на страницу фото.

Для этого нужно загрузить данные, которые ожидает +page.svelte. Удобный способ — использовать preloadData внутри обработчика click элемента <a>. Если элемент (или его родитель) использует атрибут data-sveltekit-preload-data, данные уже будут запрошены, и preloadData повторно использует этот запрос.

src/routes/photos/+page.svelte
<script>
import { preloadData, pushState, goto } from '$app/navigation';
import { page } from '$app/state';
import Modal from './Modal.svelte';
import PhotoPage from './[id]/+page.svelte';
let { data } = $props();
</script>
{#each data.thumbnails as thumbnail}
<a
href="/photos/{thumbnail.id}"
onclick={async (e) => {
if (innerWidth < 640 // выходим, если экран слишком маленький
|| e.shiftKey // или ссылка открывается в новом окне
|| e.metaKey || e.ctrlKey // или в новой вкладке (mac: metaKey, win/linux: ctrlKey)
// также стоит учитывать клик колёсиком мыши
) return;
// предотвращаем навигацию
e.preventDefault();
const { href } = e.currentTarget;
// запускаем функции `load` (точнее, получаем результат функций `load`,
// которые уже выполняются благодаря `data-sveltekit-preload-data`)
const result = await preloadData(href);
if (result.type === 'loaded' && result.status === 200) {
pushState(href, { selected: result.data });
} else {
// что-то пошло не так! попробуем перейти обычным способом
goto(href);
}
}}
>
<img alt={thumbnail.alt} src={thumbnail.src} />
</a>
{/each}
{#if page.state.selected}
<Modal onclose={() => history.back()}>
<!-- передаём данные страницы в компонент +page.svelte
так же, как это сделал бы SvelteKit при навигации -->
<PhotoPage data={page.state.selected} />
</Modal>
{/if}

Поверхностная навигация требует наличия JavaScript. Используйте её с осторожностью и по возможности предусмотрите разумное резервное поведение на случай, если JavaScript недоступен.

Сервер не имеет информации о состоянии истории браузера. Поэтому во время SSR page.state представляет собой пустой объект, а при перезагрузке страницы произойдёт переход непосредственно к предыдущему page.shallow.url, если он существует. Иными словами, если до перезагрузки page.shallow.url.pathname равно /photos/123, то после перезагрузки page.url.pathname будет равно /photos/123, а page.shallow станет null независимо от значения параметра persistState. (Это относится только к первоначальной загрузке страницы, чтобы избежать мерцания интерфейса при запуске клиентского приложения.)