SvelteKit 最新中文文档教程(3)—— 数据加载

前言

Svelte,一个语法简洁、入门容易,面向未来的前端框架。

从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1

Svelte 以其独特的编译时优化机制著称,具有轻量级高性能易上手 等特性,非常适合构建轻量级 Web 项目

为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。

如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

欢迎围观我的"网页版朋友圈"、加入"冴羽·成长陪伴社群",踏上"前端大佬成长之路"

数据加载

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

页面数据

一个 +page.svelte 文件可以有一个同级的 +page.js 文件,该文件导出一个 load 函数,该函数的返回值可以通过 data 属性在页面中使用:

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

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

!LEGACY\] 在 Svelte 4 中,您需要使用 `export let data` 代替

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

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

一个更贴合实际的博客文章 load 函数示例,它只在服务端上运行并从数据库中获取数据。可能如下所示:

js 复制代码
/// file: src/routes/blog/[slug]/+page.server.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
	export function getPost(slug: string): Promise<{ title: string, content: string }>
}

// @filename: index.js
// ---cut---
import * as db from '$lib/server/database';

/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
	return {
		post: await db.getPost(params.slug)
	};
}

注意类型从 PageLoad 变为 PageServerLoad,因为服务端 load 函数可以访问额外的参数。要了解何时使用 +page.js 和何时使用 +page.server.js文档:高级路由 请参阅 Universal 与 server

布局数据

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

js 复制代码
/// file: src/routes/blog/[slug]/+layout.server.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
	export function getPostSummaries(): Promise<Array<{ title: string, slug: string }>>
}

// @filename: index.js
// ---cut---
import * as db from '$lib/server/database';

/** @type {import('./$types').LayoutServerLoad} */
export async function load() {
	return {
		posts: await db.getPostSummaries()
	};
}
svelte 复制代码
<!--- file: src/routes/blog/[slug]/+layout.svelte --->
<script>
	/** @type {{ data: import('./$types').LayoutData, children: Snippet }} */
	let { data, children } = $props();
</script>

<main>
	<!-- +page.svelte 在此处被 `@render` -->
	{@render children()}
</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 组件以及它"所属"的布局都可用。

svelte 复制代码
/// file: src/routes/blog/[slug]/+page.svelte
<script>
	+++import { page } from '$app/state';+++

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

+++	// 我们可以访问 `data.posts` 因为它是从
	// 父布局的 `load` 函数返回的
	let index = $derived(data.posts.findIndex(post => post.slug === page.params.slug));
	let next = $derived(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}+++

!NOTE\] 如果多个 `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 实现:

svelte 复制代码
<!--- file: src/routes/+layout.svelte --->
<script>
	import { page } from '$app/state';
</script>

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

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

!LEGACY\] \> `$app/state` 是在 SvelteKit 2.12 中添加的。如果您使用的是早期版本或使用 Svelte 4,请使用 `$app/stores` 代替。它提供了一个具有相同接口的 `page` store,您可以订阅它,例如 `$page.data.title`。

Universal vs server

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

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

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

何时运行哪个 load 函数?

服务端 load 函数总是在服务端上运行。

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

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

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

输入

通用和服务端 load 函数都可以访问描述请求的属性(paramsrouteurl)以及各种函数(fetchsetHeadersparentdependsuntrack)。这些在后面的章节中会描述。

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

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

输出

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

服务端 load 函数必须返回可以用 devalue 序列化的数据 - 任何可以用 JSON 表示的内容,以及像 BigIntDateMapSetRegExp 这样的内容,或重复/循环引用 - 这样它才能通过网络传输。您的数据可以包含promises,在这种情况下它将被流式传输到浏览器。

何时使用哪个

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

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

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

js 复制代码
/// file: src/routes/+page.server.js
/** @type {import('./$types').PageServerLoad} */
export async function load() {
	return {
		serverMessage: 'hello from server load function'
	};
}
js 复制代码
/// file: src/routes/+page.js
// @errors: 18047
/** @type {import('./$types').PageLoad} */
export async function load({ data }) {
	return {
		serverMessage: data.serverMessage,
		universalMessage: 'hello from universal load function'
	};
}

使用 URL 数据

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

url

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

!NOTE\] 在某些环境中,这是在服务端渲染期间从请求头派生的。例如,如果您使用 [adapter-node](https://link.juejin.cn?target=https%3A%2F%2Fsvelte.yayujs.com%2Fdocs%2Fkit%2Fadapter-node "https://svelte.yayujs.com/docs/kit/adapter-node"),您可能需要配置适配器以使 URL 正确。

route

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

js 复制代码
/// file: src/routes/a/[b]/[...c]/+page.js
/** @type {import('./$types').PageLoad} */
export function load({ route }) {
	console.log(route.id); // '/a/[b]/[...c]'
}

params

params 是从 url.pathnameroute.id 派生的。

给定一个 route.id/a/[b]/[...c]url.pathname/a/x/y/z 时,params 对象将如下所示:

json 复制代码
{
	"b": "x",
	"c": "y/z"
}

发起 fetch 请求

要从外部 API 或 +server.js 处理程序获取数据,您可以使用提供的 fetch 函数,它的行为与原生 fetch web API完全相同,但有一些额外的功能:

  • 它可以在服务端上发起带凭据的请求,因为它继承了页面请求的 cookieauthorization 标头。
  • 它可以在服务端上发起相对请求(通常,当在服务端上下文中使用时,fetch 需要带有源的 URL)。
  • 内部请求(例如对 +server.js 路由的请求)在服务端上运行时直接转到处理函数,无需 HTTP 调用的开销。
  • 在服务端渲染期间,通过钩入 textjsonarrayBuffer 方法来捕获响应并将其内联到渲染的 HTML 中 Response 对象。请注意,除非通过 filterSerializedResponseHeaders 显式包含,否则标头将不会被序列化。
  • 在水合过程中,响应将从 HTML 中读取,确保一致性并防止额外的网络请求 - 如果在使用浏览器 fetch 而不是 loadfetch 时,在浏览器控制台中收到警告,这就是原因。
js 复制代码
/// file: src/routes/items/[id]/+page.js
/** @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 };
}

Cookies

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

js 复制代码
/// file: src/routes/+layout.server.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
  export function getUser(sessionid: string | undefined): Promise<{ name: string, avatar: string }>
}

// @filename: index.js
// ---cut---
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)
  };
}

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

例如,如果 SvelteKit 正在为 my.domain.com 提供服务:

当设置 credentials: 'include' 时,其他 cookies 将不会被传递,因为 SvelteKit 无法知道哪个 cookie 属于哪个域(浏览器不会传递这些信息),所以转发任何 cookie 都是不安全的。使用 handleFetch hook 钩子来解决这个问题。

Headers

服务端和通用 load 函数都可以访问 setHeaders 函数,当在服务端上运行时,可以为响应设置头部信息。(在浏览器中运行时,setHeaders 不会产生效果。)这在你想要缓存页面时很有用,例如:

js 复制代码
// @errors: 2322 1360
/// file: src/routes/products/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, setHeaders }) {
	const url = `https://cms.example.com/products.json`;
	const response = await fetch(url);

	// Headers are only set during SSR, caching the page's HTML
	// 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 函数时,每个标头只能设置一次。你不能使用 setHeaders 添加 set-cookie 标头 --- 应该使用cookies.set(name, value, options) 代替。

使用父级数据

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

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

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

!NOTE\] 注意,`+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.js 文件"遮蔽"的父级 +layout.server.js 文件中的数据。

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

js 复制代码
/// file: +page.js
// @filename: ambient.d.ts
declare function getData(params: Record<string, string>): Promise<{ meta: any }>

// @filename: index.js
// ---cut---
/** @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 }
  };
}

Errors

如果在 load 期间抛出错误,将渲染最近的 +error.svelte。对于预期的错误,使用来自 @sveltejs/kiterror 辅助函数来指定 HTTP 状态码和可选消息:

js 复制代码
/// file: src/routes/admin/+layout.server.js
// @filename: ambient.d.ts
declare namespace App {
  interface Locals {
    user?: {
      name: string;
      isAdmin: boolean;
    }
  }
}

// @filename: index.js
// ---cut---
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');
  }
}

调用 error(...) 将抛出一个异常,这使得在辅助函数内部停止执行变得容易。

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

!NOTE\] 在 [SvelteKit 1.x](https://link.juejin.cn?target=https%3A%2F%2Fsvelte.yayujs.com%2Fdocs%2Fkit%2Fmigrating-to-sveltekit-2%23redirect-and-error-are-no-longer-thrown-by-you "https://svelte.yayujs.com/docs/kit/migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you") 中,你必须自己 `throw` 错误

Redirects

要重定向用户,请使用来自 @sveltejs/kitredirect 辅助函数,以指定用户应被重定向到的位置以及一个 3xx 状态码。与 error(...) 类似,调用 redirect(...) 将抛出一个异常,这使得在辅助函数内部停止执行变得容易。

js 复制代码
/// file: src/routes/user/+layout.server.js
// @filename: ambient.d.ts
declare namespace App {
  interface Locals {
    user?: {
      name: string;
    }
  }
}

// @filename: index.js
// ---cut---
import { redirect } from '@sveltejs/kit';

/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
  if (!locals.user) {
    redirect(307, '/login');
  }
}

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

在浏览器中,你也可以在 load 函数之外使用来自 $app.navigationgoto 通过编程的方式进行导航。

!NOTE\] 在 [SvelteKit 1.x](https://link.juejin.cn?target=https%3A%2F%2Fsvelte.yayujs.com%2Fdocs%2Fkit%2Fmigrating-to-sveltekit-2%23redirect-and-error-are-no-longer-thrown-by-you "https://svelte.yayujs.com/docs/kit/migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you") 中,你必须自己 `throw` 这个 `redirect`

Streaming with promises

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

js 复制代码
/// file: src/routes/blog/[slug]/+page.server.js
// @filename: ambient.d.ts
declare global {
  const loadPost: (slug: string) => Promise<{ title: string, content: string }>;
  const loadComments: (slug: string) => Promise<{ content: string }>;
}

export {};

// @filename: index.js
// ---cut---
/** @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)
  };
}

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

svelte 复制代码
<!--- file: src/routes/blog/[slug]/+page.svelte --->
<script>
  /** @type {{ data: import('./$types').PageData }} */
  let { data } = $props();
</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 rejections。具体来说,如果懒加载的 Promise 在渲染开始前失败(此时会被捕获)且没有以某种方式处理错误,服务器可能会因 "unhandled promise rejection" 错误而崩溃。

当在 load 函数中直接使用 SvelteKit 的 fetch 时,SvelteKit 会为您处理这种情况。对于其他 Promise,只需为 Promise 添加一个空的 catch 即可将其标记为已处理。

js 复制代码
/// file: src/routes/+page.server.js
/** @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()
	};
}

!NOTE\] 在不支持流式传输的平台上(如 AWS Lambda 或 Firebase),响应将被缓冲。这意味着页面只会在所有 promise resolve 后才会渲染。如果您使用代理(例如 NGINX),请确保它不会缓冲来自代理服务器的响应。 \[!NOTE\] 流式数据传输只有在启用 JavaScript 时才能工作。如果页面是服务端渲染的,您应该避免从通用 `load` 函数返回 promise,因为这些 promise 不会被流式传输 ------ 相反,当函数在浏览器中重新运行时,promise 会被重新创建。 \[!NOTE\] 一旦响应开始流式传输,就无法更改响应的标头和状态码,因此您无法 `setHeaders` 或抛出重定向到流式 promise 内。 \[!NOTE\] 在 [SvelteKit 1.x](https://link.juejin.cn?target=https%3A%2F%2Fsvelte.yayujs.com%2Fdocs%2Fkit%2Fmigrating-to-sveltekit-2%23Top-level-promises-are-no-longer-awaited "https://svelte.yayujs.com/docs/kit/migrating-to-sveltekit-2#Top-level-promises-are-no-longer-awaited") 中,顶层 promise 会自动 awaited,只有嵌套的 promise 才会流式传输。

并行加载

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

重新运行 load 函数

SvelteKit 会追踪每个 load 函数的依赖关系,以避免在导航过程中不必要的重新运行。

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

js 复制代码
/// file: src/routes/blog/[slug]/+page.server.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
  export function getPost(slug: string): Promise<{ title: string, content: string }>
}

// @filename: index.js
// ---cut---
import * as db from '$lib/server/database';

/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
  return {
    post: await db.getPost(params.slug)
  };
}
js 复制代码
/// file: src/routes/blog/[slug]/+layout.server.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
  export function getPostSummaries(): Promise<Array<{ title: string, slug: string }>>
}

// @filename: index.js
// ---cut---
import * as db from '$lib/server/database';

/** @type {import('./$types').LayoutServerLoad} */
export async function load() {
  return {
    posts: await db.getPostSummaries()
  };
}

...其中 +page.server.js 中的函数在从 /blog/trying-the-raw-meat-diet 导航到 /blog/i-regret-my-choices 时会重新运行,因为 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 函数实现这一点:

js 复制代码
/// file: src/routes/+page.js
/** @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!' };
	}
}

手动失效

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

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

js 复制代码
/// file: src/routes/random-number/+page.js
/** @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()
	};
}
svelte 复制代码
<!--- file: src/routes/random-number/+page.svelte --->
<script>
  import { invalidate, invalidateAll } from '$app/navigation';

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

  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 onclick={rerunLoadFunction}>Update random number</button>

load 函数何时重新运行?

总的来说,load 函数在以下情况下会重新运行:

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

paramsurl 可以在响应 <a href=".."> 链接点击、<form> 交互goto 调用或 重定向 时发生变化。

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

对身份验证的影响

数据加载的几个特性对身份验证有重要影响:

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

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

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

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

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

拓展阅读

Svelte 中文文档

点击查看中文文档 - SvelteKit 数据加载

系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

此外我还写过 JavaScript 系列TypeScript 系列React 系列Next.js 系列冴羽答读者问等 14 个系列文章, 全系列文章目录:github.com/mqyqingfeng...

欢迎围观我的"网页版朋友圈"、加入"冴羽·成长陪伴社群",踏上"前端大佬成长之路"

相关推荐
拉不动的猪几秒前
刷刷题35(uniapp中级实际项目问题-2)
前端·javascript·面试
bigcarp6 分钟前
理解langchain langgraph 官方文档示例代码中的MemorySaver
java·前端·langchain
FreeCultureBoy9 分钟前
从 VS Code 的插件市场下载扩展插件
前端
前端菜鸟日常19 分钟前
Webpack 和 Vite 的主要区别
前端·webpack·node.js
Clockwiseee1 小时前
js原型链污染
开发语言·javascript·原型模式
仙魁XAN1 小时前
Flutter 学习之旅 之 flutter 在设备上进行 全面屏 设置/隐藏状态栏/隐藏导航栏 设置
前端·学习·flutter
hrrrrb2 小时前
【CSS3】化神篇
前端·css·css3
木木黄木木2 小时前
HTML5拼图游戏开发经验分享
前端·html·html5
BBbila2 小时前
小程序主包方法迁移到分包-调用策略
开发语言·javascript·微信小程序