SvelteKit 页面状态与浅层路由

在 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 文档维护者。本文为中文翻译,代码及命令保留原文。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容