Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
mqyqingfeng opened this issue Mar 15, 2025 · 0 comments
Open

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

mqyqingfeng opened this issue Mar 15, 2025 · 0 comments

Comments

@mqyqingfeng
Copy link
Owner

前言

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

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

image.png

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

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

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

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

数据加载

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

页面数据

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

/// 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`
		}
	};
}
<!--- 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 函数示例,它只在服务端上运行并从数据库中获取数据。可能如下所示:

/// 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 加载数据。

/// 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()
	};
}
<!--- 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 组件以及它"所属"的布局都可用。

/// 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 实现:

<!--- 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 属性):

/// file: src/routes/+page.server.js
/** @type {import('./$types').PageServerLoad} */
export async function load() {
	return {
		serverMessage: 'hello from server load function'
	};
}
/// 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,您可能需要配置适配器以使 URL 正确。

route

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

/// 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 对象将如下所示:

{
	"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 时,在浏览器控制台中收到警告,这就是原因。
/// 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

/// 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 提供服务:

  • domain.com 将不会接收 cookies
  • my.domain.com 将会接收 cookies
  • api.domain.com 将不会接收 cookies
  • sub.my.domain.com 将会接收 cookies

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

Headers

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

// @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() 实现:

/// file: src/routes/+layout.js
/** @type {import('./$types').LayoutLoad} */
export function load() {
	return { a: 1 };
}
/// file: src/routes/abc/+layout.js
/** @type {import('./$types').LayoutLoad} */
export async function load({ parent }) {
	const { a } = await parent();
	return { b: a + 1 };
}
/// file: src/routes/abc/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ parent }) {
	const { a, b } = await parent();
	return { c: a + b };
}
<!--- 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() 的结果,所以我们应该先调用它以避免延迟渲染。

/// 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 状态码和可选消息:

/// 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 中,你必须自己 throw 错误

Redirects

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

/// 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 中,你必须自己 throw 这个 redirect

Streaming with promises

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

/// 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)
  };
}

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

<!--- 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 即可将其标记为已处理。

/// 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 中,顶层 promise 会自动 awaited,只有嵌套的 promise 才会流式传输。

并行加载

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

重新运行 load 函数

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

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

/// 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)
  };
}
/// 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 函数实现这一点:

/// 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]开头的自定义标识符:

/// 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()
	};
}
<!--- 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 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant