数据加载
在渲染一个 +page.svelte
组件(及其包含的 +layout.svelte
组件)之前,我们通常需要获取一些数据。这是通过定义 load
函数来实现的。
页面数据
一个 +page.svelte
文件可以有一个同级的 +page.js
文件,该文件导出一个 load
函数,该函数的返回值可以通过 data
属性在页面中使用:
/** @type {import('./$types').PageLoad} */
export function function load({ params }: {
params: any;
}): {
post: {
title: string;
content: string;
};
}
load({ params: any
params }) {
return {
post: {
title: string;
content: string;
}
post: {
title: string
title: `Title for ${params: any
params.slug} goes here`,
content: string
content: `Content for ${params: any
params.slug} goes here`
}
};
}
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = ({ params: Record<string, any>
The parameters of the current page - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params }) => {
return {
post: {
title: string;
content: string;
}
post: {
title: string
title: `Title for ${params: Record<string, any>
The parameters of the current page - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug} goes here`,
content: string
content: `Content for ${params: Record<string, any>
The parameters of the current page - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug} goes here`
}
};
};
<script>
/** @type {{ data: import('./$types').PageData }} */
let { data } = $props();
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
Legacy mode
在 Svelte 4 中,您需要使用
export let data
代替
得益于生成的 $types
模块,我们获得了完整的类型安全性。
+page.js
文件中的 load
函数在服务端和浏览器上都会运行(除非与 export const ssr = false
结合使用,在这种情况下它将仅在浏览器中运行)。如果您的 load
函数应该始终在服务端上运行(例如,因为它使用了私有环境变量或访问数据库),那么它应该放在 +page.server.js
中。
一个更贴合实际的博客文章 load
函数示例,它只在服务端上运行并从数据库中获取数据。可能如下所示:
import * as module "$lib/server/database"
db from '$lib/server/database';
/** @type {import('./$types').PageServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load({ params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params }) {
return {
post: {
title: string;
content: string;
}
post: await module "$lib/server/database"
db.function getPost(slug: string): Promise<{
title: string;
content: string;
}>
getPost(params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug)
};
}
import * as module "$lib/server/database"
db from '$lib/server/database';
import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad } from './$types';
export const const load: PageServerLoad
load: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad = async ({ params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params }) => {
return {
post: {
title: string;
content: string;
}
post: await module "$lib/server/database"
db.function getPost(slug: string): Promise<{
title: string;
content: string;
}>
getPost(params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug)
};
};
注意类型从 PageLoad
变为 PageServerLoad
,因为服务端 load
函数可以访问额外的参数。要了解何时使用 +page.js
和何时使用 +page.server.js
文档:高级路由 请参阅Universal 与 server。
布局数据
您的 +layout.svelte
文件也可以通过 +layout.js
或 +layout.server.js
加载数据。
import * as module "$lib/server/database"
db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load() {
return {
posts: {
title: string;
slug: string;
}[]
posts: await module "$lib/server/database"
db.function getPostSummaries(): Promise<Array<{
title: string;
slug: string;
}>>
getPostSummaries()
};
}
import * as module "$lib/server/database"
db from '$lib/server/database';
import type { type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutServerLoad } from './$types';
export const const load: LayoutServerLoad
load: type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutServerLoad = async () => {
return {
posts: {
title: string;
slug: string;
}[]
posts: await module "$lib/server/database"
db.function getPostSummaries(): Promise<Array<{
title: string;
slug: string;
}>>
getPostSummaries()
};
};
<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>
<script lang="ts">
import type { LayoutData } from './$types';
let { data, children }: { data: LayoutData, children: Snippet } = $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
组件以及它”所属”的布局都可用。
<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}
<script lang="ts">
import { page } from '$app/state';
import type { PageData } from './$types';
let { data }: { data: PageData } = $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}
如果多个
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
实现:
<script>
import { page } from '$app/state';
</script>
<svelte:head>
<title>{page.data.title}</title>
</svelte:head>
<script lang="ts">
import { page } from '$app/state';
</script>
<svelte:head>
<title>{page.data.title}</title>
</svelte:head>
page.data
的类型信息由 App.PageData
提供。
Legacy mode
>
$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
属性):
/** @type {import('./$types').PageServerLoad} */
export async function function load(): Promise<{
serverMessage: string;
}>
load() {
return {
serverMessage: string
serverMessage: 'hello from server load function'
};
}
import type { type PageServerLoad = (event: Kit.ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageServerLoad = (event: Kit.ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad } from './$types';
export const const load: PageServerLoad
load: type PageServerLoad = (event: Kit.ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageServerLoad = (event: Kit.ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad = async () => {
return {
serverMessage: string
serverMessage: 'hello from server load function'
};
};
/** @type {import('./$types').PageLoad} */
export async function function load({ data }: {
data: any;
}): Promise<{
serverMessage: any;
universalMessage: string;
}>
load({ data: any
data }) {
return {
serverMessage: any
serverMessage: data: any
data.serverMessage,
universalMessage: string
universalMessage: 'hello from universal load function'
};
}
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = async ({ data: Record<string, any> | null
Contains the data returned by the route’s server load
function (in +layout.server.js
or +page.server.js
), if any.
data }) => {
return {
serverMessage: any
serverMessage: data: Record<string, any> | null
Contains the data returned by the route’s server load
function (in +layout.server.js
or +page.server.js
), if any.
data.serverMessage,
universalMessage: string
universalMessage: 'hello from universal load function'
};
};
使用 URL 数据
通常 load
函数以某种方式依赖于 URL。为此,load
函数提供了 url
、route
和 params
。
url
URL
的一个实例,包含诸如 origin
、hostname
、pathname
和 searchParams
(包含解析后的查询字符串,作为 URLSearchParams
对象)等属性。在 load
期间无法访问 url.hash
,因为它在服务端上不可用。
在某些环境中,这是在服务端渲染期间从请求头派生的。例如,如果您使用 adapter-node,您可能需要配置适配器以使 URL 正确。
route
包含当前路由目录相对于 src/routes
的名称:
/** @type {import('./$types').PageLoad} */
export function function load({ route }: {
route: any;
}): void
load({ route: any
route }) {
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object’s methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log(route: any
route.id); // '/a/[b]/[...c]'
}
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = ({ route: {
id: string | null;
}
Info about the current route
route }) => {
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object’s methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log(route: {
id: string | null;
}
Info about the current route
route.id: string | null
The ID of the current route - e.g. for src/routes/blog/[slug]
, it would be /blog/[slug]
id); // '/a/[b]/[...c]'
};
params
params
是从 url.pathname
和 route.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完全相同,但有一些额外的功能:
- 它可以在服务端上发起带凭据的请求,因为它继承了页面请求的
cookie
和authorization
标头。 - 它可以在服务端上发起相对请求(通常,当在服务端上下文中使用时,
fetch
需要带有源的 URL)。 - 内部请求(例如对
+server.js
路由的请求)在服务端上运行时直接转到处理函数,无需 HTTP 调用的开销。 - 在服务端渲染期间,通过钩入
text
、json
和arrayBuffer
方法来捕获响应并将其内联到渲染的 HTML 中 Response 对象。请注意,除非通过filterSerializedResponseHeaders
显式包含,否则标头将不会被序列化。 - 在水合过程中,响应将从 HTML 中读取,确保一致性并防止额外的网络请求 - 如果在使用浏览器
fetch
而不是loadfetch
时,在浏览器控制台中收到警告,这就是原因。
/** @type {import('./$types').PageLoad} */
export async function function load({ fetch, params }: {
fetch: any;
params: any;
}): Promise<{
item: any;
}>
load({ fetch: any
fetch, params: any
params }) {
const const res: any
res = await fetch: any
fetch(`/api/items/${params: any
params.id}`);
const const item: any
item = await const res: any
res.json();
return { item: any
item };
}
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = async ({ fetch: {
(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>;
}
fetch
is equivalent to the native fetch
web API, with a few additional features:
- It can be used to make credentialed requests on the server, as it inherits the
cookie
and authorization
headers for the page request.
- It can make relative requests on the server (ordinarily,
fetch
requires a URL with an origin when used in a server context).
- Internal requests (e.g. for
+server.js
routes) go directly to the handler function when running on the server, without the overhead of an HTTP call.
- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the
text
and json
methods of the Response
object. Note that headers will not be serialized, unless explicitly included via filterSerializedResponseHeaders
- During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request.
You can learn more about making credentialed requests with cookies here
fetch, params: Record<string, any>
The parameters of the current page - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params }) => {
const const res: Response
res = await fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)
fetch(`/api/items/${params: Record<string, any>
The parameters of the current page - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.id}`);
const const item: any
item = await const res: Response
res.Body.json(): Promise<any>
json();
return { item: any
item };
};
Cookies
服务端 load
函数可以获取和设置cookies
。
import * as module "$lib/server/database"
db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load({ cookies: Cookies
Get or set cookies related to the current request
cookies }) {
const const sessionid: string | undefined
sessionid = cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.get(name: string, opts?: CookieParseOptions): string | undefined
Gets a cookie that was previously set with cookies.set
, or from the request headers.
get('sessionid');
return {
user: {
name: string;
avatar: string;
}
user: await module "$lib/server/database"
db.function getUser(sessionid: string | undefined): Promise<{
name: string;
avatar: string;
}>
getUser(const sessionid: string | undefined
sessionid)
};
}
import * as module "$lib/server/database"
db from '$lib/server/database';
import type { type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutServerLoad } from './$types';
export const const load: LayoutServerLoad
load: type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutServerLoad = async ({ cookies: Cookies
Get or set cookies related to the current request
cookies }) => {
const const sessionid: string | undefined
sessionid = cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.get(name: string, opts?: CookieParseOptions): string | undefined
Gets a cookie that was previously set with cookies.set
, or from the request headers.
get('sessionid');
return {
user: {
name: string;
avatar: string;
}
user: await module "$lib/server/database"
db.function getUser(sessionid: string | undefined): Promise<{
name: string;
avatar: string;
}>
getUser(const sessionid: string | undefined
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 不会产生效果。)这在你想要缓存页面时很有用,例如:
/** @type {import('./$types').PageLoad} */
export async function function load({ fetch, setHeaders }: {
fetch: any;
setHeaders: any;
}): Promise<any>
load({ fetch: any
fetch, setHeaders: any
setHeaders }) {
const const url: "https://cms.example.com/products.json"
url = `https://cms.example.com/products.json`;
const const response: any
response = await fetch: any
fetch(const url: "https://cms.example.com/products.json"
url);
// Headers are only set during SSR, caching the page's HTML
// for the same length of time as the underlying data.
setHeaders: any
setHeaders({
age: any
age: const response: any
response.headers.get('age'),
'cache-control': const response: any
response.headers.get('cache-control')
});
return const response: any
response.json();
}
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = async ({ fetch: {
(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>;
}
fetch
is equivalent to the native fetch
web API, with a few additional features:
- It can be used to make credentialed requests on the server, as it inherits the
cookie
and authorization
headers for the page request.
- It can make relative requests on the server (ordinarily,
fetch
requires a URL with an origin when used in a server context).
- Internal requests (e.g. for
+server.js
routes) go directly to the handler function when running on the server, without the overhead of an HTTP call.
- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the
text
and json
methods of the Response
object. Note that headers will not be serialized, unless explicitly included via filterSerializedResponseHeaders
- During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request.
You can learn more about making credentialed requests with cookies here
fetch, setHeaders: (headers: Record<string, string>) => void
If you need to set headers for the response, you can do so using the this method. This is useful if you want the page to be cached, for example:
src/routes/blog/+pageexport async function load({ fetch, setHeaders }) {
const url = `https://cms.example.com/articles.json`;
const response = await fetch(url);
setHeaders({
age: response.headers.get('age'),
'cache-control': response.headers.get('cache-control')
});
return response.json();
}
Setting the same header multiple times (even in separate load
functions) is an error — you can only set a given header once.
You cannot add a set-cookie
header with setHeaders
— use the cookies
API in a server-only load
function instead.
setHeaders
has no effect when a load
function runs in the browser.
setHeaders }) => {
const const url: "https://cms.example.com/products.json"
url = `https://cms.example.com/products.json`;
const const response: Response
response = await fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)
fetch(const url: "https://cms.example.com/products.json"
url);
// Headers are only set during SSR, caching the page's HTML
// for the same length of time as the underlying data.
setHeaders: (headers: Record<string, string>) => void
If you need to set headers for the response, you can do so using the this method. This is useful if you want the page to be cached, for example:
src/routes/blog/+pageexport async function load({ fetch, setHeaders }) {
const url = `https://cms.example.com/articles.json`;
const response = await fetch(url);
setHeaders({
age: response.headers.get('age'),
'cache-control': response.headers.get('cache-control')
});
return response.json();
}
Setting the same header multiple times (even in separate load
functions) is an error — you can only set a given header once.
You cannot add a set-cookie
header with setHeaders
— use the cookies
API in a server-only load
function instead.
setHeaders
has no effect when a load
function runs in the browser.
setHeaders({
age: string | null
age: const response: Response
response.Response.headers: Headers
headers.Headers.get(name: string): string | null
get('age'),
'cache-control': const response: Response
response.Response.headers: Headers
headers.Headers.get(name: string): string | null
get('cache-control')
});
return const response: Response
response.Body.json(): Promise<any>
json();
};
多次设置相同的标头(即使在不同的 load
函数中)是一个错误。使用 setHeaders
函数时,每个标头只能设置一次。你不能使用 setHeaders
添加 set-cookie
标头 — 应该使用cookies.set(name, value, options)
代替。
使用父级数据
有时候让 load
函数访问父级 load
函数中的数据是很有用的,这可以通过 await parent()
实现:
/** @type {import('./$types').LayoutLoad} */
export function function load(): {
a: number;
}
load() {
return { a: number
a: 1 };
}
import type { type LayoutLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type LayoutLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutLoad } from './$types';
export const const load: LayoutLoad
load: type LayoutLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type LayoutLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutLoad = () => {
return { a: number
a: 1 };
};
/** @type {import('./$types').LayoutLoad} */
export async function function load({ parent }: {
parent: any;
}): Promise<{
b: any;
}>
load({ parent: any
parent }) {
const { const a: any
a } = await parent: any
parent();
return { b: any
b: const a: any
a + 1 };
}
import type { type LayoutLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type LayoutLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutLoad } from './$types';
export const const load: LayoutLoad
load: type LayoutLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type LayoutLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutLoad = async ({ parent: () => Promise<Record<string, any>>
await parent()
returns data from parent +layout.js
load
functions.
Implicitly, a missing +layout.js
is treated as a ({ data }) => data
function, meaning that it will return and forward data from parent +layout.server.js
files.
Be careful not to introduce accidental waterfalls when using await parent()
. If for example you only want to merge parent data into the returned output, call it after fetching your other data.
parent }) => {
const { const a: any
a } = await parent: () => Promise<Record<string, any>>
await parent()
returns data from parent +layout.js
load
functions.
Implicitly, a missing +layout.js
is treated as a ({ data }) => data
function, meaning that it will return and forward data from parent +layout.server.js
files.
Be careful not to introduce accidental waterfalls when using await parent()
. If for example you only want to merge parent data into the returned output, call it after fetching your other data.
parent();
return { b: any
b: const a: any
a + 1 };
};
/** @type {import('./$types').PageLoad} */
export async function function load({ parent }: {
parent: any;
}): Promise<{
c: any;
}>
load({ parent: any
parent }) {
const { const a: any
a, const b: any
b } = await parent: any
parent();
return { c: any
c: const a: any
a + const b: any
b };
}
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = async ({ parent: () => Promise<Record<string, any>>
await parent()
returns data from parent +layout.js
load
functions.
Implicitly, a missing +layout.js
is treated as a ({ data }) => data
function, meaning that it will return and forward data from parent +layout.server.js
files.
Be careful not to introduce accidental waterfalls when using await parent()
. If for example you only want to merge parent data into the returned output, call it after fetching your other data.
parent }) => {
const { const a: any
a, const b: any
b } = await parent: () => Promise<Record<string, any>>
await parent()
returns data from parent +layout.js
load
functions.
Implicitly, a missing +layout.js
is treated as a ({ data }) => data
function, meaning that it will return and forward data from parent +layout.server.js
files.
Be careful not to introduce accidental waterfalls when using await parent()
. If for example you only want to merge parent data into the returned output, call it after fetching your other data.
parent();
return { c: any
c: const a: any
a + const b: any
b };
};
<script>
/** @type {{ data: import('./$types').PageData }} */
let { data } = $props();
</script>
<!-- renders `1 + 2 = 3` -->
<p>{data.a} + {data.b} = {data.c}</p>
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
</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.js
文件”遮蔽”的父级 +layout.server.js
文件中的数据。
使用 await parent()
时要注意避免瀑布流。例如,getData(params)
并不依赖于调用 parent()
的结果,所以我们应该先调用它以避免延迟渲染。
/** @type {import('./$types').PageLoad} */
export async function function load(event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load({ params: Record<string, any>
The parameters of the current page - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params, parent: () => Promise<Record<string, any>>
await parent()
returns data from parent +layout.js
load
functions.
Implicitly, a missing +layout.js
is treated as a ({ data }) => data
function, meaning that it will return and forward data from parent +layout.server.js
files.
Be careful not to introduce accidental waterfalls when using await parent()
. If for example you only want to merge parent data into the returned output, call it after fetching your other data.
parent }) {
const parentData = await parent();
const const data: {
meta: any;
}
data = await function getData(params: Record<string, string>): Promise<{
meta: any;
}>
getData(params: Record<string, any>
The parameters of the current page - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params);
const const parentData: Record<string, any>
parentData = await parent: () => Promise<Record<string, any>>
await parent()
returns data from parent +layout.js
load
functions.
Implicitly, a missing +layout.js
is treated as a ({ data }) => data
function, meaning that it will return and forward data from parent +layout.server.js
files.
Be careful not to introduce accidental waterfalls when using await parent()
. If for example you only want to merge parent data into the returned output, call it after fetching your other data.
parent();
return {
...const data: {
meta: any;
}
data,
meta: any
meta: { ...const parentData: Record<string, any>
parentData.meta, ...const data: {
meta: any;
}
data.meta: any
meta }
};
}
import type { type PageLoad = (event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = async ({ params: Record<string, any>
The parameters of the current page - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params, parent: () => Promise<Record<string, any>>
await parent()
returns data from parent +layout.js
load
functions.
Implicitly, a missing +layout.js
is treated as a ({ data }) => data
function, meaning that it will return and forward data from parent +layout.server.js
files.
Be careful not to introduce accidental waterfalls when using await parent()
. If for example you only want to merge parent data into the returned output, call it after fetching your other data.
parent }) => {
const parentData = await parent();
const const data: {
meta: any;
}
data = await function getData(params: Record<string, string>): Promise<{
meta: any;
}>
getData(params: Record<string, any>
The parameters of the current page - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params);
const const parentData: Record<string, any>
parentData = await parent: () => Promise<Record<string, any>>
await parent()
returns data from parent +layout.js
load
functions.
Implicitly, a missing +layout.js
is treated as a ({ data }) => data
function, meaning that it will return and forward data from parent +layout.server.js
files.
Be careful not to introduce accidental waterfalls when using await parent()
. If for example you only want to merge parent data into the returned output, call it after fetching your other data.
parent();
return {
...const data: {
meta: any;
}
data,
meta: any
meta: { ...const parentData: Record<string, any>
parentData.meta, ...const data: {
meta: any;
}
data.meta: any
meta }
};
};
Errors
如果在 load
期间抛出错误,将渲染最近的 +error.svelte
。对于预期的错误,使用来自 @sveltejs/kit
的 error
辅助函数来指定 HTTP 状态码和可选消息:
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
/** @type {import('./$types').LayoutServerLoad} */
export function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load({ locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals }) {
if (!locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.App.Locals.user?: {
name: string;
isAdmin: boolean;
} | undefined
user) {
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(401, 'not logged in');
}
if (!locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.App.Locals.user?: {
name: string;
isAdmin: boolean;
}
user.isAdmin: boolean
isAdmin) {
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(403, 'not an admin');
}
}
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
import type { type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutServerLoad } from './$types';
export const const load: LayoutServerLoad
load: type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutServerLoad = ({ locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals }) => {
if (!locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.App.Locals.user?: {
name: string;
isAdmin: boolean;
} | undefined
user) {
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(401, 'not logged in');
}
if (!locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.App.Locals.user?: {
name: string;
isAdmin: boolean;
}
user.isAdmin: boolean
isAdmin) {
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(403, 'not an admin');
}
};
调用 error(...)
将抛出一个异常,这使得在辅助函数内部停止执行变得容易。
如果抛出了一个意外错误,SvelteKit 将调用 handleError
并将其视为 500 内部错误。
在 SvelteKit 1.x 中,你必须自己
throw
错误
Redirects
要重定向用户,请使用来自 @sveltejs/kit
的 redirect
辅助函数,以指定用户应被重定向到的位置以及一个 3xx
状态码。与 error(...)
类似,调用 redirect(...)
将抛出一个异常,这使得在辅助函数内部停止执行变得容易。
import { function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never
Redirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
redirect } from '@sveltejs/kit';
/** @type {import('./$types').LayoutServerLoad} */
export function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load({ locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals }) {
if (!locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.App.Locals.user?: {
name: string;
} | undefined
user) {
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never
Redirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
redirect(307, '/login');
}
}
import { function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never
Redirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
redirect } from '@sveltejs/kit';
import type { type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutServerLoad } from './$types';
export const const load: LayoutServerLoad
load: type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutServerLoad = ({ locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals }) => {
if (!locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.App.Locals.user?: {
name: string;
} | undefined
user) {
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never
Redirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
redirect(307, '/login');
}
};
不要在
try {...}
块内使用redirect()
,因为重定向会立即触发 catch 语句。
在浏览器中,你也可以在 load
函数之外使用来自 $app.navigation
的 goto
通过编程的方式进行导航。
在 SvelteKit 1.x 中,你必须自己
throw
这个redirect
Streaming with promises
当使用服务端 load
时,Promise 将在 resolve 时流式传输到浏览器。如果你有较慢的、非必要的数据,这很有用,因为你可以在所有数据可用之前开始渲染页面:
/** @type {import('./$types').PageServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load({ params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params }) {
return {
// make sure the `await` happens at the end, otherwise we
// can't start loading comments until we've loaded the post
comments: Promise<{
content: string;
}>
comments: const loadComments: (slug: string) => Promise<{
content: string;
}>
loadComments(params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug),
post: {
title: string;
content: string;
}
post: await const loadPost: (slug: string) => Promise<{
title: string;
content: string;
}>
loadPost(params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug)
};
}
import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad } from './$types';
export const const load: PageServerLoad
load: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad = async ({ params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params }) => {
return {
// make sure the `await` happens at the end, otherwise we
// can't start loading comments until we've loaded the post
comments: Promise<{
content: string;
}>
comments: const loadComments: (slug: string) => Promise<{
content: string;
}>
loadComments(params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug),
post: {
title: string;
content: string;
}
post: await const loadPost: (slug: string) => Promise<{
title: string;
content: string;
}>
loadPost(params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug)
};
};
这对创建骨架加载状态很有用,例如:
<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}
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $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
即可将其标记为已处理。
/** @type {import('./$types').PageServerLoad} */
export function function load({ fetch }: {
fetch: any;
}): {
ok_manual: Promise<never>;
ok_fetch: any;
dangerous_unhandled: Promise<never>;
}
load({ fetch: any
fetch }) {
const const ok_manual: Promise<never>
ok_manual = var Promise: PromiseConstructor
Represents the completion of an asynchronous operation
Promise.PromiseConstructor.reject<never>(reason?: any): Promise<never>
Creates a new rejected promise for the provided reason.
reject();
const ok_manual: Promise<never>
ok_manual.Promise<never>.catch<void>(onrejected?: ((reason: any) => void | PromiseLike<void>) | null | undefined): Promise<void>
Attaches a callback for only the rejection of the Promise.
catch(() => {});
return {
ok_manual: Promise<never>
ok_manual,
ok_fetch: any
ok_fetch: fetch: any
fetch('/fetch/that/could/fail'),
dangerous_unhandled: Promise<never>
dangerous_unhandled: var Promise: PromiseConstructor
Represents the completion of an asynchronous operation
Promise.PromiseConstructor.reject<never>(reason?: any): Promise<never>
Creates a new rejected promise for the provided reason.
reject()
};
}
import type { type PageServerLoad = (event: Kit.ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageServerLoad = (event: Kit.ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad } from './$types';
export const const load: PageServerLoad
load: type PageServerLoad = (event: Kit.ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageServerLoad = (event: Kit.ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad = ({ fetch: {
(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>;
}
fetch
is equivalent to the native fetch
web API, with a few additional features:
- It can be used to make credentialed requests on the server, as it inherits the
cookie
and authorization
headers for the page request.
- It can make relative requests on the server (ordinarily,
fetch
requires a URL with an origin when used in a server context).
- Internal requests (e.g. for
+server.js
routes) go directly to the handler function when running on the server, without the overhead of an HTTP call.
- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the
text
and json
methods of the Response
object. Note that headers will not be serialized, unless explicitly included via filterSerializedResponseHeaders
- During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request.
You can learn more about making credentialed requests with cookies here
fetch }) => {
const const ok_manual: Promise<never>
ok_manual = var Promise: PromiseConstructor
Represents the completion of an asynchronous operation
Promise.PromiseConstructor.reject<never>(reason?: any): Promise<never>
Creates a new rejected promise for the provided reason.
reject();
const ok_manual: Promise<never>
ok_manual.Promise<never>.catch<void>(onrejected?: ((reason: any) => void | PromiseLike<void>) | null | undefined): Promise<void>
Attaches a callback for only the rejection of the Promise.
catch(() => {});
return {
ok_manual: Promise<never>
ok_manual,
ok_fetch: Promise<Response>
ok_fetch: fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)
fetch('/fetch/that/could/fail'),
dangerous_unhandled: Promise<never>
dangerous_unhandled: var Promise: PromiseConstructor
Represents the completion of an asynchronous operation
Promise.PromiseConstructor.reject<never>(reason?: any): Promise<never>
Creates a new rejected promise for the provided reason.
reject()
};
};
在不支持流式传输的平台上(如 AWS Lambda 或 Firebase),响应将被缓冲。这意味着页面只会在所有 promise resolve 后才会渲染。如果您使用代理(例如 NGINX),请确保它不会缓冲来自代理服务器的响应。
流式数据传输只有在启用 JavaScript 时才能工作。如果页面是服务端渲染的,您应该避免从通用
load
函数返回 promise,因为这些 promise 不会被流式传输 —— 相反,当函数在浏览器中重新运行时,promise 会被重新创建。
一旦响应开始流式传输,就无法更改响应的标头和状态码,因此您无法
setHeaders
或抛出重定向到流式 promise 内。
在 SvelteKit 1.x 中,顶层 promise 会自动 awaited,只有嵌套的 promise 才会流式传输。
并行加载
在渲染(或导航到)页面时,SvelteKit 会同时运行所有 load
函数,避免请求瀑布。在客户端导航期间,多个服务器 load
函数的调用结果会被组合到单个响应中。一旦所有 load
函数都返回结果,页面就会被渲染。
重新运行 load 函数
SvelteKit 会追踪每个 load
函数的依赖关系,以避免在导航过程中不必要的重新运行。
例如,给定一对这样的 load
函数...
import * as module "$lib/server/database"
db from '$lib/server/database';
/** @type {import('./$types').PageServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load({ params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params }) {
return {
post: {
title: string;
content: string;
}
post: await module "$lib/server/database"
db.function getPost(slug: string): Promise<{
title: string;
content: string;
}>
getPost(params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug)
};
}
import * as module "$lib/server/database"
db from '$lib/server/database';
import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad } from './$types';
export const const load: PageServerLoad
load: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad = async ({ params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params }) => {
return {
post: {
title: string;
content: string;
}
post: await module "$lib/server/database"
db.function getPost(slug: string): Promise<{
title: string;
content: string;
}>
getPost(params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug)
};
};
import * as module "$lib/server/database"
db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load() {
return {
posts: {
title: string;
slug: string;
}[]
posts: await module "$lib/server/database"
db.function getPostSummaries(): Promise<Array<{
title: string;
slug: string;
}>>
getPostSummaries()
};
}
import * as module "$lib/server/database"
db from '$lib/server/database';
import type { type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutServerLoad } from './$types';
export const const load: LayoutServerLoad
load: type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutServerLoad = async () => {
return {
posts: {
title: string;
slug: string;
}[]
posts: await module "$lib/server/database"
db.function getPostSummaries(): Promise<Array<{
title: string;
slug: string;
}>>
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
函数实现这一点:
/** @type {import('./$types').PageLoad} */
export async function function load({ untrack, url }: {
untrack: any;
url: any;
}): Promise<{
message: string;
} | undefined>
load({ untrack: any
untrack, url: any
url }) {
// Untrack url.pathname so that path changes don't trigger a rerun
if (untrack: any
untrack(() => url: any
url.pathname === '/')) {
return { message: string
message: 'Welcome!' };
}
}
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = async ({ untrack: <T>(fn: () => T) => T
Use this function to opt out of dependency tracking for everything that is synchronously called within the callback. Example:
src/routes/+page.serverexport async function load({ untrack, url }) {
// Untrack url.pathname so that path changes don't trigger a rerun
if (untrack(() => url.pathname === '/')) {
return { message: 'Welcome!' };
}
}
untrack, url: URL
The URL of the current page
url }) => {
// Untrack url.pathname so that path changes don't trigger a rerun
if (untrack: <boolean>(fn: () => boolean) => boolean
Use this function to opt out of dependency tracking for everything that is synchronously called within the callback. Example:
src/routes/+page.serverexport async function load({ untrack, url }) {
// Untrack url.pathname so that path changes don't trigger a rerun
if (untrack(() => url.pathname === '/')) {
return { message: 'Welcome!' };
}
}
untrack(() => url: URL
The URL of the current page
url.URL.pathname: string
pathname === '/')) {
return { message: string
message: 'Welcome!' };
}
};
手动失效
你还可以使用 invalidate(url)
重新运行适用于当前页面的 load
函数,它会重新运行所有依赖于 url
的 load
函数,以及使用 invalidateAll()
重新运行每个 load
函数。服务端加载函数永远不会自动依赖于获取数据的 url
,以避免将秘密泄露给客户端。
如果一个 load
函数调用了 fetch(url)
或 depends(url)
,那么它就依赖于 url
。注意,url
可以是以 [a-z]
开头的自定义标识符:
/** @type {import('./$types').PageLoad} */
export async function function load({ fetch, depends }: {
fetch: any;
depends: any;
}): Promise<{
number: any;
}>
load({ fetch: any
fetch, depends: any
depends }) {
// load reruns when `invalidate('https://api.example.com/random-number')` is called...
const const response: any
response = await fetch: any
fetch('https://api.example.com/random-number');
// ...or when `invalidate('app:random')` is called
depends: any
depends('app:random');
return {
number: any
number: await const response: any
response.json()
};
}
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = async ({ fetch: {
(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>;
}
fetch
is equivalent to the native fetch
web API, with a few additional features:
- It can be used to make credentialed requests on the server, as it inherits the
cookie
and authorization
headers for the page request.
- It can make relative requests on the server (ordinarily,
fetch
requires a URL with an origin when used in a server context).
- Internal requests (e.g. for
+server.js
routes) go directly to the handler function when running on the server, without the overhead of an HTTP call.
- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the
text
and json
methods of the Response
object. Note that headers will not be serialized, unless explicitly included via filterSerializedResponseHeaders
- During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request.
You can learn more about making credentialed requests with cookies here
fetch, depends: (...deps: Array<`${string}:${string}`>) => void
This function declares that the load
function has a dependency on one or more URLs or custom identifiers, which can subsequently be used with invalidate()
to cause load
to rerun.
Most of the time you won’t need this, as fetch
calls depends
on your behalf — it’s only necessary if you’re using a custom API client that bypasses fetch
.
URLs can be absolute or relative to the page being loaded, and must be encoded.
Custom identifiers have to be prefixed with one or more lowercase letters followed by a colon to conform to the URI specification.
The following example shows how to use depends
to register a dependency on a custom identifier, which is invalidate
d after a button click, making the load
function rerun.
src/routes/+pagelet count = 0;
export async function load({ depends }) {
depends('increase:count');
return { count: count++ };
}
src/routes/+page<script>
import { invalidate } from '$app/navigation';
let { data } = $props();
const increase = async () => {
await invalidate('increase:count');
}
</script>
<p>{data.count}<p>
<button on:click={increase}>Increase Count</button>
depends }) => {
// load reruns when `invalidate('https://api.example.com/random-number')` is called...
const const response: Response
response = await fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)
fetch('https://api.example.com/random-number');
// ...or when `invalidate('app:random')` is called
depends: (...deps: Array<`${string}:${string}`>) => void
This function declares that the load
function has a dependency on one or more URLs or custom identifiers, which can subsequently be used with invalidate()
to cause load
to rerun.
Most of the time you won’t need this, as fetch
calls depends
on your behalf — it’s only necessary if you’re using a custom API client that bypasses fetch
.
URLs can be absolute or relative to the page being loaded, and must be encoded.
Custom identifiers have to be prefixed with one or more lowercase letters followed by a colon to conform to the URI specification.
The following example shows how to use depends
to register a dependency on a custom identifier, which is invalidate
d after a button click, making the load
function rerun.
src/routes/+pagelet count = 0;
export async function load({ depends }) {
depends('increase:count');
return { count: count++ };
}
src/routes/+page<script>
import { invalidate } from '$app/navigation';
let { data } = $props();
const increase = async () => {
await invalidate('increase:count');
}
</script>
<p>{data.count}<p>
<button on:click={increase}>Increase Count</button>
depends('app:random');
return {
number: any
number: await const response: Response
response.Body.json(): Promise<any>
json()
};
};
<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>
<script lang="ts">
import { invalidate, invalidateAll } from '$app/navigation';
import type { PageData } from './$types';
let { data }: { data: PageData } = $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()
返回的数据,否则其他选项会更有性能优势。