在 SvelteKit 应用中导航时,会创建历史记录条目。点击后退或前进按钮,会沿记录列表移动,并按需重新运行 load 函数、替换页面组件、更新滚动位置和焦点元素。
有时,希望创建历史条目,却不执行完整导航。这称为浅层路由。
例如,可以显示一个允许用户通过后退关闭的模态对话框。移动设备上,滑动手势通常比直接操作界面更自然,因此这尤其有价值。如果模态框未关联历史条目,用户向后滑动以关闭它时,可能反而跳到其他页面,造成困惑。
SvelteKit 的 goto 配合 shallow: true,可以在不执行完整导航的情况下,为历史条目关联状态。
+page 的 JavaScript 版本:
<script>
import { goto } from '$app/navigation';
import { page } from '$app/state';
import Modal from './Modal.svelte';
function showModal() {
goto('', {
state: { showModal: true },
shallow: true
});
}
</script>
<button onclick={showModal}>open</button>
{#if page.state.showModal}
<Modal close={() => history.back()} />
{/if}
TypeScript 版本:
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/state';
import Modal from './Modal.svelte';
function showModal() {
goto('', {
state: { showModal: true },
shallow: true
});
}
</script>
<button onclick={showModal}>open</button>
{#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
});
如果用户重新加载,将进入 /another/page,而不是当前渲染的页面,详见后面的注意事项。
无论是否修改可见 URL,beforeNavigate、onNavigate 和 afterNavigate 都会运行,其中 navigation.type === 'goto',navigation.shallow === true。
浅层路由启用后,page.shallow 变为 { url, params, route } 对象,描述用户真正导航到该位置时会渲染的页面,例如重新加载后的页面。page.url、page.params 和 page.route 仍描述当前实际渲染的页面。
JavaScript 示例:
<script>
import { goto } from '$app/navigation';
import { page } from '$app/state';
</script>
<p>The user-visible URL is {page.shallow?.url.href ?? page.url.href}</p>
<p>The actual page you're on is {page.url.href}</p>
<button onclick={() => goto('/shallow', { shallow: true })}>enter shallow route</button>
TypeScript 示例:
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/state';
</script>
<p>The user-visible URL is {page.shallow?.url.href ?? page.url.href}</p>
<p>The actual page you're on is {page.url.href}</p>
<button onclick={() => goto('/shallow', { shallow: true })}>enter shallow route</button>
不带 shallow: true 的普通 goto,或者常规链接点击,会退出浅层路由。
通过后退或前进返回浅层条目,会恢复其 page.state 与 page.shallow。渲染页面及 page.url 仍是最初调用 goto 时所在的页面。要导航到地址栏显示的 URL,调用不带 shallow: true 的 goto(page.shallow.url)。
旧模式:SvelteKit 2 使用 pushState 和 replaceState 实现这项功能,现在它们已弃用。应改用 goto 与 shallow: true;替换当前历史条目时再使用 replace。
路由选项
上面的示例默认向历史栈添加新条目。如果不希望这样,可以替换现有条目:
goto(url, {
state,
replace: true
});
通过 goto 设置的状态,默认不会在重新加载后恢复。要改变这一点,使用 persistState:
goto(url, {
state,
persistState: true
});
浅层导航默认保留当前滚动位置与焦点元素。可以通过 reset: true 取消此行为:
goto(url, {
shallow: true,
reset: true
});
page.state 仅在 JavaScript 加载后填充,可能造成界面闪烁,使用时应谨慎。
加载路由数据
使用浅层路由时,可能希望在当前页面中渲染另一个 +page.svelte。例如点击照片缩略图,弹出详情,而不真正导航到照片页面。
为此,需要加载目标 +page.svelte 期待的数据。方便的做法是在 <a> 点击处理器中调用 preloadData。如果链接或其父元素使用 data-sveltekit-preload-data,数据已经被请求,preloadData 会复用请求。
src/routes/photos/+page 的 JavaScript 版本:
<script>
import { preloadData, 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 // bail if the screen is too small
|| e.shiftKey // or the link is opened in a new window
|| e.metaKey || e.ctrlKey // or a new tab (mac: metaKey, win/linux: ctrlKey)
// should also consider clicking with a mouse scroll wheel
) return;
// prevent navigation
e.preventDefault();
const { href } = e.currentTarget;
// run `load` functions (or rather, get the result of the `load` functions
// that are already running because of `data-sveltekit-preload-data`)
const result = await preloadData(href);
if (result.type === 'loaded' && result.status === 200) {
goto(href, { shallow: true, state: { selected: result.data } });
} else {
// something bad happened! try navigating
goto(href);
}
}}
>
<img alt={thumbnail.alt} src={thumbnail.src} />
</a>
{/each}
{#if page.state.selected}
<Modal onclose={() => history.back()}>
<!-- pass page data to the +page.svelte component,
just like SvelteKit would on navigation -->
<PhotoPage data={page.state.selected} />
</Modal>
{/if}
TypeScript 版本:
<script lang="ts">
import { preloadData, 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 // bail if the screen is too small
|| e.shiftKey // or the link is opened in a new window
|| e.metaKey || e.ctrlKey // or a new tab (mac: metaKey, win/linux: ctrlKey)
// should also consider clicking with a mouse scroll wheel
) return;
// prevent navigation
e.preventDefault();
const { href } = e.currentTarget;
// run `load` functions (or rather, get the result of the `load` functions
// that are already running because of `data-sveltekit-preload-data`)
const result = await preloadData(href);
if (result.type === 'loaded' && result.status === 200) {
goto(href, { shallow: true, state: { selected: result.data } });
} else {
// something bad happened! try navigating
goto(href);
}
}}
>
<img alt={thumbnail.alt} src={thumbnail.src} />
</a>
{/each}
{#if page.state.selected}
<Modal onclose={() => history.back()}>
<!-- pass page data to the +page.svelte component,
just like SvelteKit would on navigation -->
<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 选项影响。这只适用于首次页面加载,以避免客户端应用启动时界面闪烁。
原文:Page state & shallow routing。作者/维护方:SvelteKit 文档维护者。本文为中文翻译,代码及命令保留原文。











暂无评论内容