数据加载
在渲染一个 +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: anyparams }) {
return {
post: {
title: string;
content: string;
}
post: {
title: stringtitle: `Title for ${params: anyparams.slug} goes here`,
content: stringcontent: `Content for ${params: anyparams.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: PageLoadload: 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: stringtitle: `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: stringcontent: `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: PageServerLoadload: 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: LayoutServerLoadload: 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代替。它提供了一个具有相同接口的pagestore,您可以订阅它,例如$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: stringserverMessage: '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: PageServerLoadload: 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: stringserverMessage: 'hello from server load function'
};
};/** @type {import('./$types').PageLoad} */
export async function function load({ data }: {
data: any;
}): Promise<{
serverMessage: any;
universalMessage: string;
}>
load({ data: anydata }) {
return {
serverMessage: anyserverMessage: data: anydata.serverMessage,
universalMessage: stringuniversalMessage: '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: PageLoadload: 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> | nullContains the data returned by the route’s server load function (in +layout.server.js or +page.server.js), if any.
data }) => {
return {
serverMessage: anyserverMessage: data: Record<string, any> | nullContains the data returned by the route’s server load function (in +layout.server.js or +page.server.js), if any.
data.serverMessage,
universalMessage: stringuniversalMessage: '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: anyroute }) {
var console: ConsoleThe 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: anyroute.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: PageLoadload: 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: ConsoleThe 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 | nullThe 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: anyfetch, params: anyparams }) {
const const res: anyres = await fetch: anyfetch(`/api/items/${params: anyparams.id}`);
const const item: anyitem = await const res: anyres.json();
return { item: anyitem };
}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: PageLoadload: 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: Responseres = 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: anyitem = await const res: Responseres.Body.json(): Promise<any>json();
return { item: anyitem };
};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: CookiesGet or set cookies related to the current request
cookies }) {
const const sessionid: string | undefinedsessionid = cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.get(name: string, opts?: CookieParseOptions): string | undefinedGets 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 | undefinedsessionid)
};
}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: LayoutServerLoadload: type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>LayoutServerLoad = async ({ cookies: CookiesGet or set cookies related to the current request
cookies }) => {
const const sessionid: string | undefinedsessionid = cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.get(name: string, opts?: CookieParseOptions): string | undefinedGets 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 | undefinedsessionid)
};
};只有当目标主机与 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: anyfetch, setHeaders: anysetHeaders }) {
const const url: "https://cms.example.com/products.json"url = `https://cms.example.com/products.json`;
const const response: anyresponse = await fetch: anyfetch(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: anysetHeaders({
age: anyage: const response: anyresponse.headers.get('age'),
'cache-control': const response: anyresponse.headers.get('cache-control')
});
return const response: anyresponse.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: PageLoadload: 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>) => voidIf 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: Responseresponse = 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>) => voidIf 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 | nullage: const response: Responseresponse.Response.headers: Headersheaders.Headers.get(name: string): string | nullget('age'),
'cache-control': const response: Responseresponse.Response.headers: Headersheaders.Headers.get(name: string): string | nullget('cache-control')
});
return const response: Responseresponse.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: numbera: 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: LayoutLoadload: 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: numbera: 1 };
};/** @type {import('./$types').LayoutLoad} */
export async function function load({ parent }: {
parent: any;
}): Promise<{
b: any;
}>
load({ parent: anyparent }) {
const { const a: anya } = await parent: anyparent();
return { b: anyb: const a: anya + 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: LayoutLoadload: 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: anya } = 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: anyb: const a: anya + 1 };
};/** @type {import('./$types').PageLoad} */
export async function function load({ parent }: {
parent: any;
}): Promise<{
c: any;
}>
load({ parent: anyparent }) {
const { const a: anya, const b: anyb } = await parent: anyparent();
return { c: anyc: const a: anya + const b: anyb };
}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: PageLoadload: 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: anya, const b: anyb } = 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: anyc: const a: anya + const b: anyb };
};<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: anymeta: { ...const parentData: Record<string, any>parentData.meta, ...const data: {
meta: any;
}
data.meta: anymeta }
};
}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: PageLoadload: 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: anymeta: { ...const parentData: Record<string, any>parentData.meta, ...const data: {
meta: any;
}
data.meta: anymeta }
};
};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.LocalsContains custom data that was added to the request within the server handle hook.
locals }) {
if (!locals: App.LocalsContains 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.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user?: {
name: string;
isAdmin: boolean;
}
user.isAdmin: booleanisAdmin) {
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: LayoutServerLoadload: type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>LayoutServerLoad = ({ locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals }) => {
if (!locals: App.LocalsContains 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.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user?: {
name: string;
isAdmin: boolean;
}
user.isAdmin: booleanisAdmin) {
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): neverRedirect 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.LocalsContains custom data that was added to the request within the server handle hook.
locals }) {
if (!locals: App.LocalsContains 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): neverRedirect 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): neverRedirect 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: LayoutServerLoadload: type LayoutServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>LayoutServerLoad = ({ locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals }) => {
if (!locals: App.LocalsContains 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): neverRedirect 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: PageServerLoadload: 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: anyfetch }) {
const const ok_manual: Promise<never>ok_manual = var Promise: PromiseConstructorRepresents 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: anyok_fetch: fetch: anyfetch('/fetch/that/could/fail'),
dangerous_unhandled: Promise<never>dangerous_unhandled: var Promise: PromiseConstructorRepresents 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: PageServerLoadload: 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: PromiseConstructorRepresents 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: PromiseConstructorRepresents 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: PageServerLoadload: 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: LayoutServerLoadload: 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: anyuntrack, url: anyurl }) {
// Untrack url.pathname so that path changes don't trigger a rerun
if (untrack: anyuntrack(() => url: anyurl.pathname === '/')) {
return { message: stringmessage: '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: PageLoadload: 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) => TUse 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: URLThe URL of the current page
url }) => {
// Untrack url.pathname so that path changes don't trigger a rerun
if (untrack: <boolean>(fn: () => boolean) => booleanUse 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: URLThe URL of the current page
url.URL.pathname: stringpathname === '/')) {
return { message: stringmessage: '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: anyfetch, depends: anydepends }) {
// load reruns when `invalidate('https://api.example.com/random-number')` is called...
const const response: anyresponse = await fetch: anyfetch('https://api.example.com/random-number');
// ...or when `invalidate('app:random')` is called
depends: anydepends('app:random');
return {
number: anynumber: await const response: anyresponse.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: PageLoadload: 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}`>) => voidThis 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 invalidated 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: Responseresponse = 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}`>) => voidThis 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 invalidated 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: anynumber: await const response: Responseresponse.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.jsload函数中直接使用身份验证守卫进行特定路由保护
在 +layout.server.js 中放置身份验证守卫要求所有子页面在受保护代码之前调用 await parent()。除非每个子页面都依赖于await parent() 返回的数据,否则其他选项会更有性能优势。