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

前言

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 属性在页面中使用:

1/// file: src/routes/blog/[slug]/+page.js 2/** @type {import('./$types').PageLoad} */ 3export function load({ params }) { 4 return { 5 post: { 6 title: `Title for ${params.slug} goes here`, 7 content: `Content for ${params.slug} goes here` 8 } 9 }; 10}
1<!--- file: src/routes/blog/[slug]/+page.svelte ---> 2<script> 3 /** @type {{ data: import('./$types').PageData }} */ 4 let { data } = $props(); 5</script> 6 7<h1>{data.post.title}</h1> 8<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 函数示例,它只在服务端上运行并从数据库中获取数据。可能如下所示:

1/// file: src/routes/blog/[slug]/+page.server.js 2// @filename: ambient.d.ts 3declare module '$lib/server/database' { 4 export function getPost(slug: string): Promise<{ title: string, content: string }> 5} 6 7// @filename: index.js 8// ---cut--- 9import * as db from '$lib/server/database'; 10 11/** @type {import('./$types').PageServerLoad} */ 12export async function load({ params }) { 13 return { 14 post: await db.getPost(params.slug) 15 }; 16}

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

布局数据

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

1/// file: src/routes/blog/[slug]/+layout.server.js 2// @filename: ambient.d.ts 3declare module '$lib/server/database' { 4 export function getPostSummaries(): Promise<Array<{ title: string, slug: string }>> 5} 6 7// @filename: index.js 8// ---cut--- 9import * as db from '$lib/server/database'; 10 11/** @type {import('./$types').LayoutServerLoad} */ 12export async function load() { 13 return { 14 posts: await db.getPostSummaries() 15 }; 16}
1<!--- file: src/routes/blog/[slug]/+layout.svelte ---> 2<script> 3 /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */ 4 let { data, children } = $props(); 5</script> 6 7<main> 8 <!-- +page.svelte 在此处被 `@render` --> 9 {@render children()} 10</main> 11 12<aside> 13 <h2>More posts</h2> 14 <ul> 15 {#each data.posts as post} 16 <li> 17 <a href="/blog/{post.slug}"> 18 {post.title} 19 </a> 20 </li> 21 {/each} 22 </ul> 23</aside>

布局 load 函数返回的数据对子 +layout.svelte 组件和 +page.svelte 组件以及它"所属"的布局都可用。

1/// file: src/routes/blog/[slug]/+page.svelte 2<script> 3 +++import { page } from '$app/state';+++ 4 5 /** @type {{ data: import('./$types').PageData }} */ 6 let { data } = $props(); 7 8+++ // 我们可以访问 `data.posts` 因为它是从 9 // 父布局的 `load` 函数返回的 10 let index = $derived(data.posts.findIndex(post => post.slug === page.params.slug)); 11 let next = $derived(data.posts[index + 1]);+++ 12</script> 13 14<h1>{data.post.title}</h1> 15<div>{@html data.post.content}</div> 16 17+++{#if next} 18 <p>Next post: <a href="/blog/{next.slug}">{next.title}</a></p> 19{/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 实现:

1<!--- file: src/routes/+layout.svelte ---> 2<script> 3 import { page } from '$app/state'; 4</script> 5 6<svelte:head> 7 <title>{page.data.title}</title> 8</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 属性):

1/// file: src/routes/+page.server.js 2/** @type {import('./$types').PageServerLoad} */ 3export async function load() { 4 return { 5 serverMessage: 'hello from server load function' 6 }; 7}
1/// file: src/routes/+page.js 2// @errors: 18047 3/** @type {import('./$types').PageLoad} */ 4export async function load({ data }) { 5 return { 6 serverMessage: data.serverMessage, 7 universalMessage: 'hello from universal load function' 8 }; 9}

使用 URL 数据

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

url

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

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

route

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

1/// file: src/routes/a/[b]/[...c]/+page.js 2/** @type {import('./$types').PageLoad} */ 3export function load({ route }) { 4 console.log(route.id); // '/a/[b]/[...c]' 5}

params

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

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

1{ 2 "b": "x", 3 "c": "y/z" 4}

发起 fetch 请求

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

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

Cookies

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

1/// file: src/routes/+layout.server.js 2// @filename: ambient.d.ts 3declare module '$lib/server/database' { 4 export function getUser(sessionid: string | undefined): Promise<{ name: string, avatar: string }> 5} 6 7// @filename: index.js 8// ---cut--- 9import * as db from '$lib/server/database'; 10 11/** @type {import('./$types').LayoutServerLoad} */ 12export async function load({ cookies }) { 13 const sessionid = cookies.get('sessionid'); 14 15 return { 16 user: await db.getUser(sessionid) 17 }; 18}

只有当目标主机与 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 不会产生效果。)这在你想要缓存页面时很有用,例如:

1// @errors: 2322 1360 2/// file: src/routes/products/+page.js 3/** @type {import('./$types').PageLoad} */ 4export async function load({ fetch, setHeaders }) { 5 const url = `https://cms.example.com/products.json`; 6 const response = await fetch(url); 7 8 // Headers are only set during SSR, caching the page's HTML 9 // for the same length of time as the underlying data. 10 setHeaders({ 11 age: response.headers.get('age'), 12 'cache-control': response.headers.get('cache-control') 13 }); 14 15 return response.json(); 16}

多次设置相同的标头(即使在不同的 load 函数中)是一个错误。使用 setHeaders 函数时,每个标头只能设置一次。你不能使用 setHeaders 添加 set-cookie 标头 — 应该使用cookies.set(name, value, options) 代替。

使用父级数据

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

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

1/// file: +page.js 2// @filename: ambient.d.ts 3declare function getData(params: Record<string, string>): Promise<{ meta: any }> 4 5// @filename: index.js 6// ---cut--- 7/** @type {import('./$types').PageLoad} */ 8export async function load({ params, parent }) { 9 ---const parentData = await parent();--- 10 const data = await getData(params); 11 +++const parentData = await parent();+++ 12 13 return { 14 ...data, 15 meta: { ...parentData.meta, ...data.meta } 16 }; 17}

Errors

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

1/// file: src/routes/admin/+layout.server.js 2// @filename: ambient.d.ts 3declare namespace App { 4 interface Locals { 5 user?: { 6 name: string; 7 isAdmin: boolean; 8 } 9 } 10} 11 12// @filename: index.js 13// ---cut--- 14import { error } from '@sveltejs/kit'; 15 16/** @type {import('./$types').LayoutServerLoad} */ 17export function load({ locals }) { 18 if (!locals.user) { 19 error(401, 'not logged in'); 20 } 21 22 if (!locals.user.isAdmin) { 23 error(403, 'not an admin'); 24 } 25}

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

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

[!NOTE] 在 SvelteKit 1.x 中,你必须自己 throw 错误

Redirects

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

1/// file: src/routes/user/+layout.server.js 2// @filename: ambient.d.ts 3declare namespace App { 4 interface Locals { 5 user?: { 6 name: string; 7 } 8 } 9} 10 11// @filename: index.js 12// ---cut--- 13import { redirect } from '@sveltejs/kit'; 14 15/** @type {import('./$types').LayoutServerLoad} */ 16export function load({ locals }) { 17 if (!locals.user) { 18 redirect(307, '/login'); 19 } 20}

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

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

[!NOTE] 在 SvelteKit 1.x 中,你必须自己 throw 这个 redirect

Streaming with promises

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

1/// file: src/routes/blog/[slug]/+page.server.js 2// @filename: ambient.d.ts 3declare global { 4 const loadPost: (slug: string) => Promise<{ title: string, content: string }>; 5 const loadComments: (slug: string) => Promise<{ content: string }>; 6} 7 8export {}; 9 10// @filename: index.js 11// ---cut--- 12/** @type {import('./$types').PageServerLoad} */ 13export async function load({ params }) { 14 return { 15 // make sure the `await` happens at the end, otherwise we 16 // can't start loading comments until we've loaded the post 17 comments: loadComments(params.slug), 18 post: await loadPost(params.slug) 19 }; 20}

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

1<!--- file: src/routes/blog/[slug]/+page.svelte ---> 2<script> 3 /** @type {{ data: import('./$types').PageData }} */ 4 let { data } = $props(); 5</script> 6 7<h1>{data.post.title}</h1> 8<div>{@html data.post.content}</div> 9 10{#await data.comments} 11 Loading comments... 12{:then comments} 13 {#each comments as comment} 14 <p>{comment.content}</p> 15 {/each} 16{:catch error} 17 <p>error loading comments: {error.message}</p> 18{/await}

在流式传输数据时,请注意正确处理 Promise rejections。具体来说,如果懒加载的 Promise 在渲染开始前失败(此时会被捕获)且没有以某种方式处理错误,服务器可能会因 "unhandled promise rejection" 错误而崩溃。

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

1/// file: src/routes/+page.server.js 2/** @type {import('./$types').PageServerLoad} */ 3export function load({ fetch }) { 4 const ok_manual = Promise.reject(); 5 ok_manual.catch(() => {}); 6 7 return { 8 ok_manual, 9 ok_fetch: fetch('/fetch/that/could/fail'), 10 dangerous_unhandled: Promise.reject() 11 }; 12}

[!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 函数...

1/// file: src/routes/blog/[slug]/+page.server.js 2// @filename: ambient.d.ts 3declare module '$lib/server/database' { 4 export function getPost(slug: string): Promise<{ title: string, content: string }> 5} 6 7// @filename: index.js 8// ---cut--- 9import * as db from '$lib/server/database'; 10 11/** @type {import('./$types').PageServerLoad} */ 12export async function load({ params }) { 13 return { 14 post: await db.getPost(params.slug) 15 }; 16}
1/// file: src/routes/blog/[slug]/+layout.server.js 2// @filename: ambient.d.ts 3declare module '$lib/server/database' { 4 export function getPostSummaries(): Promise<Array<{ title: string, slug: string }>> 5} 6 7// @filename: index.js 8// ---cut--- 9import * as db from '$lib/server/database'; 10 11/** @type {import('./$types').LayoutServerLoad} */ 12export async function load() { 13 return { 14 posts: await db.getPostSummaries() 15 }; 16}

...其中 +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 函数实现这一点:

1/// file: src/routes/+page.js 2/** @type {import('./$types').PageLoad} */ 3export async function load({ untrack, url }) { 4 // Untrack url.pathname so that path changes don't trigger a rerun 5 if (untrack(() => url.pathname === '/')) { 6 return { message: 'Welcome!' }; 7 } 8}

手动失效

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

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

1/// file: src/routes/random-number/+page.js 2/** @type {import('./$types').PageLoad} */ 3export async function load({ fetch, depends }) { 4 // load reruns when `invalidate('https://api.example.com/random-number')` is called... 5 const response = await fetch('https://api.example.com/random-number'); 6 7 // ...or when `invalidate('app:random')` is called 8 depends('app:random'); 9 10 return { 11 number: await response.json() 12 }; 13}
1<!--- file: src/routes/random-number/+page.svelte ---> 2<script> 3 import { invalidate, invalidateAll } from '$app/navigation'; 4 5 /** @type {{ data: import('./$types').PageData }} */ 6 let { data } = $props(); 7 8 function rerunLoadFunction() { 9 // any of these will cause the `load` function to rerun 10 invalidate('app:random'); 11 invalidate('https://api.example.com/random-number'); 12 invalidate(url => url.href.includes('random-number')); 13 invalidateAll(); 14 } 15</script> 16 17<p>random number: {data.number}</p> 18<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

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

点赞
收藏

评论区

加载中...

相关推荐

Svelte 最新中文文档教程(21)—— 自定义元素

前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非

Svelte 最新中文文档教程(22)—— Svelte 5 迁移指南

前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非

SvelteKit 最新中文文档教程(1)—— 入门指南

前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非

SvelteKit 最新中文文档教程(2)—— 路由

前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非

SvelteKit 最新中文文档教程(4)—— 表单 actions

前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非

SvelteKit 最新中文文档教程(5)—— 页面选项

前言,一个语法简洁、入门容易,面向未来的前端框架。从Svelte诞生之初,就备受开发者的喜爱,根据统计,从2019年到2024年,连续6年一直是开发者最感兴趣的前端框架No.1:Svelte以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非

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