跳至主要内容

核心概念

加载数据

在 GitHub 上编辑此页面

+page.svelte 组件(及其包含的 +layout.svelte 组件)可以被渲染之前,我们通常需要获取一些数据。这是通过定义 load 函数来完成的。

页面数据

+page.svelte 文件可以有一个兄弟 +page.js 文件,它导出一个 load 函数,其返回值通过 data prop 提供给页面

src/routes/blog/[slug]/+page.js
ts
/** @type {import('./$types').PageLoad} */
export function load({ params }) {
return {
post: {
title: `Title for ${params.slug} goes here`,
content: `Content for ${params.slug} goes here`
}
};
}
src/routes/blog/[slug]/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
return {
post: {
title: `Title for ${params.slug} goes here`,
content: `Content for ${params.slug} goes here`,
},
};
};
src/routes/blog/[slug]/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>

<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
src/routes/blog/[slug]/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>

感谢生成的 $types 模块,我们获得了完全类型安全性。

+page.js 文件中的 load 函数在服务器和浏览器中同时运行(除非与 export const ssr = false 结合使用,在这种情况下它将 仅在浏览器中运行)。如果你的 load 函数应该始终在服务器上运行(例如,因为它使用私有环境变量或访问数据库),那么它应该放在 +page.server.js 中。

你的博客文章的 load 函数的一个更实际的版本,它仅在服务器上运行并从数据库中提取数据,可能如下所示

src/routes/blog/[slug]/+page.server.js
ts
import * as db from '$lib/server/database';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
return {
post: await db.getPost(params.slug)
};
}
src/routes/blog/[slug]/+page.server.ts
ts
import * as db from '$lib/server/database';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
return {
post: await db.getPost(params.slug),
};
};

请注意,类型已从 PageLoad 更改为 PageServerLoad,因为服务器 load 函数可以访问其他参数。要了解何时使用 +page.js 以及何时使用 +page.server.js,请参阅 通用与服务器

布局数据

您的 +layout.svelte 文件也可以通过 +layout.js+layout.server.js 加载数据。

src/routes/blog/[slug]/+layout.server.js
ts
import * as db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function load() {
return {
posts: await db.getPostSummaries()
};
}
src/routes/blog/[slug]/+layout.server.ts
ts
import * as db from '$lib/server/database';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async () => {
return {
posts: await db.getPostSummaries(),
};
};
src/routes/blog/[slug]/+layout.svelte
<script>
	/** @type {import('./$types').LayoutData} */
	export let data;
</script>

<main>
	<!-- +page.svelte is rendered in this <slot> -->
	<slot />
</main>

<aside>
	<h2>More posts</h2>
	<ul>
		{#each data.posts as post}
			<li>
				<a href="/blog/{post.slug}">
					{post.title}
				</a>
			</li>
		{/each}
	</ul>
</aside>
src/routes/blog/[slug]/+layout.svelte
<script lang="ts">
	import type { LayoutData } from './$types';
	
	export let data: LayoutData;
</script>

<main>
	<!-- +page.svelte is rendered in this <slot> -->
	<slot />
</main>

<aside>
	<h2>More posts</h2>
	<ul>
		{#each data.posts as post}
			<li>
				<a href="/blog/{post.slug}">
					{post.title}
				</a>
			</li>
		{/each}
	</ul>
</aside>

从布局 load 函数返回的数据可供子 +layout.svelte 组件和 +page.svelte 组件以及它“所属”的布局使用。

src/routes/blog/[slug]/+page.svelte
<script>
	import { page } from '$app/stores';

	/** @type {import('./$types').PageData} */
	export let data;

	// we can access `data.posts` because it's returned from
	// the parent layout `load` function
	$: index = data.posts.findIndex(post => post.slug === $page.params.slug);
	$: next = data.posts[index - 1];
</script>

<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>

{#if next}
	<p>Next post: <a href="/blog/{next.slug}">{next.title}</a></p>
{/if}

如果多个 load 函数返回具有相同键的数据,则最后一个“获胜”——布局 load 返回 { a: 1, b: 2 } 和页面 load 返回 { b: 3, c: 4 } 的结果将是 { a: 1, b: 3, c: 4 }

$page.data

+page.svelte 组件及其上方的每个 +layout.svelte 组件都可以访问其自身数据以及其父组件的所有数据。

在某些情况下,我们可能需要相反的情况——父布局可能需要访问页面数据或子布局中的数据。例如,根布局可能希望访问 +page.js+page.server.js 中的 load 函数返回的 title 属性。这可以通过 $page.data 完成

src/routes/+layout.svelte
<script>
	import { page } from '$app/stores';
</script>

<svelte:head>
	<title>{$page.data.title}</title>
</svelte:head>
src/routes/+layout.svelte
<script lang="ts">
	import { page } from '$app/stores';
</script>

<svelte:head>
	<title>{$page.data.title}</title>
</svelte:head>

$page.data 的类型信息由 App.PageData 提供。

通用与服务器

正如我们所见,有两种类型的 load 函数

  • +page.js+layout.js 文件导出在服务器和浏览器上都运行的通用 load 函数
  • +page.server.js+layout.server.js 文件导出仅在服务器端运行的服务器 load 函数

从概念上讲,它们是同一件事,但有一些重要的区别需要注意。

哪个加载函数在何时运行?

服务器load函数始终在服务器上运行。

默认情况下,当用户首次访问你的页面时,通用load函数在服务器端渲染 (SSR) 期间运行。它们将在水化期间再次运行,重复使用来自fetch 请求的任何响应。通用load函数的所有后续调用都在浏览器中发生。你可以通过页面选项自定义行为。如果你禁用了服务器端渲染,你将获得一个 SPA,并且通用load函数始终在客户端运行。

如果一个路由同时包含通用和服务器load函数,则服务器load函数首先运行。

除非你预渲染页面,否则load函数在运行时被调用——在这种情况下,它在构建时被调用。

输入

通用和服务器load函数都可以访问描述请求的属性(paramsrouteurl)和各种函数(fetchsetHeadersparentdependsuntrack)。这些将在以下部分中进行描述。

服务器load函数使用ServerLoadEvent调用,它从RequestEvent继承了clientAddresscookieslocalsplatformrequest

通用load函数使用LoadEvent调用,它有一个data属性。如果你在+page.js+page.server.js(或+layout.js+layout.server.js)中都有load函数,则服务器load函数的返回值是通用load函数参数的data属性。

输出

通用load函数可以返回一个包含任何值的对象,包括自定义类和组件构造函数等内容。

服务器load函数必须返回可以用devalue序列化的数据——任何可以表示为 JSON 的内容以及BigIntDateMapSetRegExp等内容,或重复/循环引用——以便它可以通过网络传输。你的数据可以包括promise,在这种情况下,它将流式传输到浏览器。

何时使用哪个

当你需要直接从数据库或文件系统访问数据,或需要使用私有环境变量时,服务器load函数很方便。

通用load函数在你需要从外部 API fetch数据并且不需要私有凭据时很有用,因为 SvelteKit 可以直接从 API 获取数据,而不是通过你的服务器。当你需要返回无法序列化的内容(例如 Svelte 组件构造函数)时,它们也很有用。

在极少数情况下,您可能需要同时使用两者 — 例如,您可能需要返回一个自定义类的实例,该实例已使用来自服务器的数据进行初始化。同时使用时,服务器 load 返回值不会直接传递到页面,而是传递到通用 load 函数(作为 data 属性)

src/routes/+page.server.js
ts
/** @type {import('./$types').PageServerLoad} */
export async function load() {
return {
serverMessage: 'hello from server load function'
};
}
src/routes/+page.server.ts
ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async () => {
return {
serverMessage: 'hello from server load function',
};
};
src/routes/+page.js
ts
/** @type {import('./$types').PageLoad} */
export async function load({ data }) {
return {
serverMessage: data.serverMessage,
universalMessage: 'hello from universal load function'
};
}
src/routes/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ data }) => {
return {
serverMessage: data.serverMessage,
universalMessage: 'hello from universal load function',
};
};

使用 URL 数据

通常,load 函数以某种方式依赖于 URL。为此,load 函数为您提供了 urlrouteparams

url

URL 的实例,包含诸如 originhostnamepathnamesearchParams(包含作为 URLSearchParams 对象解析后的查询字符串)之类的属性。在 load 期间无法访问 url.hash,因为它在服务器上不可用。

在某些环境中,这是在服务器端渲染期间从请求头派生的。例如,如果您正在使用 adapter-node,您可能需要配置适配器才能使 URL 正确。

route

包含相对于 src/routes 的当前路由目录的名称

src/routes/a/[b]/[...c]/+page.js
ts
/** @type {import('./$types').PageLoad} */
export function load({ route }) {
console.log(route.id); // '/a/[b]/[...c]'
}
src/routes/a/[b]/[...c]/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = ({ route }) => {
console.log(route.id); // '/a/[b]/[...c]'
};

params

params 派生自 url.pathnameroute.id

给定 route.id/a/[b]/[...c]url.pathname/a/x/y/zparams 对象将如下所示

ts
{
"b": "x",
"c": "y/z"
}

发出获取请求

要从外部 API 或 +server.js 处理程序获取数据,您可以使用提供的 fetch 函数,其行为与 native fetch web API 完全相同,但具有以下一些附加功能

  • 它可用于对服务器进行凭据请求,因为它继承了页面请求的 cookieauthorization 标头。
  • 它可以在服务器上进行相对请求(通常,fetch 在服务器上下文中使用时需要带有原点的 URL)。
  • 内部请求(例如对于 +server.js 路由)在服务器上运行时直接转到处理程序函数,而无需 HTTP 调用的开销。
  • 在服务器端渲染期间,将通过挂接到 Response 对象的 textjsonarrayBuffer 方法来捕获响应并将其内联到渲染的 HTML 中。请注意,标头将不会被序列化,除非通过 filterSerializedResponseHeaders 明确包含。
  • 在水化期间,将从 HTML 中读取响应,从而保证一致性并防止额外的网络请求 - 如果你在使用浏览器的 fetch 而不是 load fetch 时在浏览器控制台中收到警告,这就是原因。
src/routes/items/[id]/+page.js
ts
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, params }) {
const res = await fetch(`/api/items/${params.id}`);
const item = await res.json();
return { item };
}
src/routes/items/[id]/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch, params }) => {
const res = await fetch(`/api/items/${params.id}`);
const item = await res.json();
return { item };
};

Cookie

服务器 load 函数可以获取和设置 cookies

src/routes/+layout.server.js
ts
import * as db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function load({ cookies }) {
const sessionid = cookies.get('sessionid');
return {
user: await db.getUser(sessionid)
};
}
src/routes/+layout.server.ts
ts
import * as db from '$lib/server/database';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ cookies }) => {
const sessionid = cookies.get('sessionid');
return {
user: await db.getUser(sessionid),
};
};

只有当目标主机与 SvelteKit 应用程序或其更具体的子域相同,Cookie 才将通过提供的 fetch 函数传递。

例如,如果 SvelteKit 正在提供 my.domain.com

  • domain.com 将不会收到 Cookie
  • my.domain.com 将收到 Cookie
  • api.domain.com 将不会收到 Cookie
  • sub.my.domain.com 将收到 Cookie

当设置 credentials: 'include' 时,不会传递其他 Cookie,因为 SvelteKit 不知道哪个 Cookie 属于哪个域(浏览器不会传递此信息),因此转发任何 Cookie 都不安全。使用 handleFetch 挂钩 来解决此问题。

标头

服务器和通用 load 函数都可以访问 setHeaders 函数,该函数在服务器上运行时可以为响应设置标头。(在浏览器中运行时,setHeaders 不起作用。)例如,如果你希望页面被缓存,这将很有用

src/routes/products/+page.js
ts
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, setHeaders }) {
const url = `https://cms.example.com/products.json`;
const response = await fetch(url);
// cache the page for the same length of time
// as the underlying data
setHeaders({
age: response.headers.get('age'),
'cache-control': response.headers.get('cache-control')
});
return response.json();
}
src/routes/products/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch, setHeaders }) => {
const url = `https://cms.example.com/products.json`;
const response = await fetch(url);
// cache the page for the same length of time
// as the underlying data
setHeaders({
age: response.headers.get('age'),
'cache-control': response.headers.get('cache-control'),
});
return response.json();
};

多次设置相同的标头(即使在单独的 load 函数中)是一个错误——你只能设置给定的标头一次。你不能使用 setHeaders 添加 set-cookie 标头——请改用 cookies.set(name, value, options)

使用父级数据

有时,load 函数访问父级 load 函数中的数据很有用,这可以通过 await parent() 来实现

src/routes/+layout.js
ts
/** @type {import('./$types').LayoutLoad} */
export function load() {
return { a: 1 };
}
src/routes/+layout.ts
ts
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = () => {
return { a: 1 };
};
src/routes/abc/+layout.js
ts
/** @type {import('./$types').LayoutLoad} */
export async function load({ parent }) {
const { a } = await parent();
return { b: a + 1 };
}
src/routes/abc/+layout.ts
ts
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async ({ parent }) => {
const { a } = await parent();
return { b: a + 1 };
};
src/routes/abc/+page.js
ts
/** @type {import('./$types').PageLoad} */
export async function load({ parent }) {
const { a, b } = await parent();
return { c: a + b };
}
src/routes/abc/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ parent }) => {
const { a, b } = await parent();
return { c: a + b };
};
src/routes/abc/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>

<!-- renders `1 + 2 = 3` -->
<p>{data.a} + {data.b} = {data.c}</p>
src/routes/abc/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

<!-- renders `1 + 2 = 3` -->
<p>{data.a} + {data.b} = {data.c}</p>

请注意,+page.js 中的 load 函数接收两个布局 load 函数合并的数据,而不仅仅是直接父级。

+page.server.js+layout.server.js 内部,parent 返回父级 +layout.server.js 文件中的数据。

+page.js+layout.js 中,它将返回父级 +layout.js 文件中的数据。但是,缺少的 +layout.js 被视为 ({ data }) => data 函数,这意味着它还将返回父级 +layout.server.js 文件中的数据,这些文件没有被 +layout.js 文件“隐藏”

使用 await parent() 时,注意不要引入瀑布。例如,这里 getData(params) 不依赖于调用 parent() 的结果,所以我们应该首先调用它以避免延迟渲染。

+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ params, parent }) {
	const parentData = await parent();
	const data = await getData(params);
	const parentData = await parent();

	return {
		...data
		meta: { ...parentData.meta, ...data.meta }
	};
}

错误

如果在 load 期间抛出错误,则会渲染最近的 +error.svelte。对于预期错误,请使用 @sveltejs/kit 中的 error 帮助程序来指定 HTTP 状态代码和可选消息

src/routes/admin/+layout.server.js
ts
import { error } from '@sveltejs/kit';
/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
if (!locals.user) {
error(401, 'not logged in');
}
if (!locals.user.isAdmin) {
error(403, 'not an admin');
}
}
src/routes/admin/+layout.server.ts
ts
import { error } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = ({ locals }) => {
if (!locals.user) {
error(401, 'not logged in');
}
if (!locals.user.isAdmin) {
error(403, 'not an admin');
}
};

调用 error(...) 将抛出一个异常,从而可以轻松地从帮助程序函数内部停止执行。

如果抛出意外错误,SvelteKit 将调用 handleError 并将其视为 500 内部错误。

在 SvelteKit 1.x 中,你必须自己 throw 错误

重定向

要重定向用户,请使用 @sveltejs/kit 中的 redirect 帮助器来指定应将其重定向到的位置以及 3xx 状态代码。与 error(...) 一样,调用 redirect(...) 将抛出异常,从而可以轻松地从帮助器函数内部停止执行。

src/routes/user/+layout.server.js
ts
import { redirect } from '@sveltejs/kit';
/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
if (!locals.user) {
redirect(307, '/login');
}
}
src/routes/user/+layout.server.ts
ts
import { redirect } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = ({ locals }) => {
if (!locals.user) {
redirect(307, '/login');
}
};

不要在 try {...} 块内使用 redirect(),因为重定向将立即触发 catch 语句。

在浏览器中,你还可以使用 $app.navigation 中的 gotoload 函数之外以编程方式导航。

在 SvelteKit 1.x 中,你必须自己 throw redirect

使用 Promise 进行流式传输

使用服务器 load 时,Promise 将在解析时流式传输到浏览器。如果你有缓慢的非必要数据,这很有用,因为你可以在所有数据可用之前开始渲染页面

src/routes/blog/[slug]/+page.server.js
ts
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
return {
// make sure the `await` happens at the end, otherwise we
// can't start loading comments until we've loaded the post
comments: loadComments(params.slug),
post: await loadPost(params.slug)
};
}
src/routes/blog/[slug]/+page.server.ts
ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
return {
// make sure the `await` happens at the end, otherwise we
// can't start loading comments until we've loaded the post
comments: loadComments(params.slug),
post: await loadPost(params.slug),
};
};

例如,这对于创建骨架加载状态很有用

src/routes/blog/[slug]/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>

<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>

{#await data.comments}
	Loading comments...
{:then comments}
	{#each comments as comment}
		<p>{comment.content}</p>
	{/each}
{:catch error}
	<p>error loading comments: {error.message}</p>
{/await}
src/routes/blog/[slug]/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>

{#await data.comments}
	Loading comments...
{:then comments}
	{#each comments as comment}
		<p>{comment.content}</p>
	{/each}
{:catch error}
	<p>error loading comments: {error.message}</p>
{/await}

在流式传输数据时,请小心正确处理 Promise 拒绝。更具体地说,如果延迟加载的 Promise 在渲染开始之前失败(此时已捕获),并且没有以某种方式处理错误,则服务器可能会因“未处理的 Promise 拒绝”错误而崩溃。在 load 函数中直接使用 SvelteKit 的 fetch 时,SvelteKit 将为你处理这种情况。对于其他 Promise,将 noop-catch 附加到 Promise 以将其标记为已处理就足够了。

src/routes/+page.server.js
ts
/** @type {import('./$types').PageServerLoad} */
export function load({ fetch }) {
const ok_manual = Promise.reject();
ok_manual.catch(() => {});
return {
ok_manual,
ok_fetch: fetch('/fetch/that/could/fail'),
dangerous_unhandled: Promise.reject()
};
}
src/routes/+page.server.ts
ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = ({ fetch }) => {
const ok_manual = Promise.reject();
ok_manual.catch(() => {});
return {
ok_manual,
ok_fetch: fetch('/fetch/that/could/fail'),
dangerous_unhandled: Promise.reject(),
};
};

在不支持流式传输的平台(例如 AWS Lambda 或 Firebase)上,响应将被缓冲。这意味着页面只有在所有 Promise 解析后才会渲染。如果你正在使用代理(例如 NGINX),请确保它不会缓冲来自代理服务器的响应。

只有在启用 JavaScript 时,流式传输数据才有效。如果页面是服务器渲染的,你应该避免从通用 load 函数返回 Promise,因为它们不会被流式传输——相反,当函数在浏览器中重新运行时,Promise 会被重新创建。

响应的标头和状态代码在响应开始流式传输后无法更改,因此你不能在流式传输的 Promise 中setHeaders或抛出重定向。

在 SvelteKit 1.x 中,顶级 Promise 会自动等待,只有嵌套 Promise 会流式传输。

并行加载

在渲染(或导航到)页面时,SvelteKit 会并发运行所有load函数,避免请求瀑布。在客户端导航期间,调用多个服务器load函数的结果将分组为单个响应。一旦所有load函数返回,页面就会被渲染。

重新运行 load 函数

SvelteKit 会跟踪每个load函数的依赖项,以避免在导航期间不必要地重新运行它。

例如,给定一对这样的load函数...

src/routes/blog/[slug]/+page.server.js
ts
import * as db from '$lib/server/database';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
return {
post: await db.getPost(params.slug)
};
}
src/routes/blog/[slug]/+page.server.ts
ts
import * as db from '$lib/server/database';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
return {
post: await db.getPost(params.slug),
};
};
src/routes/blog/[slug]/+layout.server.js
ts
import * as db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function load() {
return {
posts: await db.getPostSummaries()
};
}
src/routes/blog/[slug]/+layout.server.ts
ts
import * as db from '$lib/server/database';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async () => {
return {
posts: await db.getPostSummaries(),
};
};

...如果我们从/blog/trying-the-raw-meat-diet导航到/blog/i-regret-my-choices,则+page.server.js中的函数将重新运行,因为params.slug已更改。+layout.server.js中的函数不会重新运行,因为数据仍然有效。换句话说,我们不会第二次调用db.getPostSummaries()

如果父load函数重新运行,则调用await parent()load函数也将重新运行。

依赖项跟踪不适用于load函数返回之后——例如,在嵌套promise中访问params.x不会导致函数在params.x更改时重新运行。(不用担心,如果你不小心这样做了,你将在开发中收到警告。)相反,在load函数的主体中访问参数。

搜索参数独立于 URL 的其余部分进行跟踪。例如,在load函数中访问event.url.searchParams.get("x")将使该load函数在从?x=1导航到?x=2时重新运行,但不会在从?x=1&y=1导航到?x=1&y=2时重新运行。

取消跟踪依赖项

在极少数情况下,你可能希望从依赖项跟踪机制中排除某些内容。你可以使用提供的untrack函数来实现这一点

src/routes/+page.js
ts
/** @type {import('./$types').PageLoad} */
export async function load({ untrack, url }) {
// Untrack url.pathname so that path changes don't trigger a rerun
if (untrack(() => url.pathname === '/')) {
return { message: 'Welcome!' };
}
}
src/routes/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ untrack, url }) => {
// Untrack url.pathname so that path changes don't trigger a rerun
if (untrack(() => url.pathname === '/')) {
return { message: 'Welcome!' };
}
};

手动失效

您还可以使用 invalidate(url) 重新运行适用于当前页面的 load 函数,它会重新运行所有依赖于 urlload 函数,以及 invalidateAll(),它会重新运行每个 load 函数。服务器加载函数永远不会自动依赖于已获取的 url,以避免将机密泄露给客户端。

如果 load 函数调用 fetch(url)depends(url),则它依赖于 url。请注意,url 可以是自定义标识符,它以 [a-z]: 开头:

src/routes/random-number/+page.js
ts
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, depends }) {
// load reruns when `invalidate('https://api.example.com/random-number')` is called...
const response = await fetch('https://api.example.com/random-number');
// ...or when `invalidate('app:random')` is called
depends('app:random');
return {
number: await response.json()
};
}
src/routes/random-number/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch, depends }) => {
// load reruns when `invalidate('https://api.example.com/random-number')` is called...
const response = await fetch('https://api.example.com/random-number');
// ...or when `invalidate('app:random')` is called
depends('app:random');
return {
number: await response.json(),
};
};
src/routes/random-number/+page.svelte
<script>
	import { invalidate, invalidateAll } from '$app/navigation';

	/** @type {import('./$types').PageData} */
	export let data;

	function rerunLoadFunction() {
		// any of these will cause the `load` function to rerun
		invalidate('app:random');
		invalidate('https://api.example.com/random-number');
		invalidate(url => url.href.includes('random-number'));
		invalidateAll();
	}
</script>

<p>random number: {data.number}</p>
<button on:click={rerunLoadFunction}>Update random number</button>
src/routes/random-number/+page.svelte
<script lang="ts">
	import { invalidate, invalidateAll } from '$app/navigation';
	
	import type { PageData } from './$types';
	
	export let data: PageData;
	
	function rerunLoadFunction() {
		// any of these will cause the `load` function to rerun
		invalidate('app:random');
		invalidate('https://api.example.com/random-number');
		invalidate((url) => url.href.includes('random-number'));
		invalidateAll();
	}
</script>

<p>random number: {data.number}</p>
<button on:click={rerunLoadFunction}>Update random number</button>

加载函数何时重新运行?

总之,load 函数将在以下情况下重新运行

  • 它引用了 params 的属性,其值已更改
  • 它引用了 url 的属性(例如 url.pathnameurl.search),其值已更改。request.url 中的属性不会被跟踪
  • 它调用 url.searchParams.get(...)url.searchParams.getAll(...)url.searchParams.has(...),并且相关参数发生更改。访问 url.searchParams 的其他属性将与访问 url.search 产生相同的效果。
  • 它调用 await parent(),并且父 load 函数重新运行
  • 它通过 fetch(仅限通用加载)或 depends 声明了对特定 URL 的依赖,并且该 URL 已使用 invalidate(url) 标记为无效
  • 所有活动 load 函数都已使用 invalidateAll() 强制重新运行

paramsurl 可以响应 <a href=".."> 链接点击、<form> 交互、goto 调用或 redirect 而更改。

请注意,重新运行 load 函数将更新相应 +layout.svelte+page.svelte 中的 data 属性;它不会导致重新创建组件。因此,内部状态得以保留。如果您不希望这样,您可以在 afterNavigate 回调中重置您需要重置的所有内容,和/或将您的组件包装在 {#key ...} 块中。

对身份验证的影响

加载数据的一些特性对身份验证检查有重要影响

  • 布局 load 函数不会在每个请求上运行,例如在子路由之间的客户端导航期间。 (加载函数何时重新运行?)
  • 布局和页面 load 函数并发运行,除非调用了 await parent()。如果布局 load 抛出异常,则页面 load 函数会运行,但客户端不会收到返回的数据。

有几种可能的策略可以确保在受保护代码之前进行身份验证检查。

防止数据瀑布并保留布局load缓存

  • 在任何load函数运行之前,使用hooks保护多个路由
  • +page.server.js load函数中直接使用身份验证保护,以实现特定路由保护

+layout.server.js中放置身份验证保护需要所有子页面在受保护代码之前调用await parent()。除非每个子页面都依赖于await parent()返回的数据,否则其他选项将具有更好的性能。

进一步阅读

上一个 路由
下一个 表单操作