前言
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
函数都可以访问描述请求的属性(params
、route
和 url
)以及各种函数(fetch
、setHeaders
、parent
、depends
和 untrack
)。这些在后面的章节中会描述。
服务端 load
函数使用 ServerLoadEvent
调用,它从 RequestEvent
继承 clientAddress
、cookies
、locals
、platform
和 request
。
通用 load
函数使用具有 data
属性的 LoadEvent
调用。如果您在 +page.js
和 +page.server.js
(或 +layout.js
和 +layout.server.js
)中都有 load
函数,则服务端 load
函数的返回值是通用 load
函数参数的 data
属性。
输出
通用 load
函数可以返回包含任何值的对象,包括自定义类和组件构造函数等内容。
服务端 load
函数必须返回可以用 devalue 序列化的数据 - 任何可以用 JSON 表示的内容,以及像 BigInt
、Date
、Map
、Set
和 RegExp
这样的内容,或重复/循环引用 - 这样它才能通过网络传输。您的数据可以包含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
函数提供了 url
、route
和 params
。
url
URL
的一个实例,包含诸如 origin
、hostname
、pathname
和 searchParams
(包含解析后的查询字符串,作为 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.pathname
和 route.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完全相同,但有一些额外的功能:
- 它可以在服务端上发起带凭据的请求,因为它继承了页面请求的
cookie
和authorization
标头。 - 它可以在服务端上发起相对请求(通常,当在服务端上下文中使用时,
fetch
需要带有源的 URL)。 - 内部请求(例如对
+server.js
路由的请求)在服务端上运行时直接转到处理函数,无需 HTTP 调用的开销。 - 在服务端渲染期间,通过钩入
text
、json
和arrayBuffer
方法来捕获响应并将其内联到渲染的 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 提供服务:
- 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 不会产生效果。)这在你想要缓存页面时很有用,例如:
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/kit
的 error
辅助函数来指定 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/kit
的 redirect
辅助函数,以指定用户应被重定向到的位置以及一个 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.navigation
的 goto
通过编程的方式进行导航。
!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
函数,它会重新运行所有依赖于 url
的 load
函数,以及使用 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.pathname
或url.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()
强制重新运行
params
和 url
可以在响应 <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...
欢迎围观我的"网页版朋友圈"、加入"冴羽·成长陪伴社群",踏上"前端大佬成长之路"。