数据加载 #
一、数据加载概述 #
SvelteKit 使用 load 函数在页面渲染前获取数据。
text
┌─────────────────────────────────────────────────────────────┐
│ 数据加载流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户访问 URL │
│ ↓ │
│ 路由匹配 │
│ ↓ │
│ 执行 load 函数 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ +layout.ts (从根到叶) │ │
│ │ +layout.server.ts │ │
│ │ +page.ts │ │
│ │ +page.server.ts │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 数据合并传递给页面 │
│ ↓ │
│ 渲染页面 │
│ │
└─────────────────────────────────────────────────────────────┘
二、load 函数 #
2.1 基本用法 #
src/routes/+page.ts:
typescript
export async function load() {
return {
message: 'Hello from load function'
};
}
src/routes/+page.svelte:
svelte
<script>
let { data } = $props();
</script>
<p>{data.message}</p>
2.2 load 参数 #
typescript
export async function load({
params, // URL 参数
url, // URL 对象
fetch, // fetch 函数
cookies, // cookies 对象
request, // Request 对象
route, // 路由信息
parent, // 父布局数据
depends, // 依赖声明
setHeaders, // 设置响应头
untrack // 取消追踪
}) {
return {};
}
2.3 获取 URL 参数 #
typescript
export async function load({ params, url }) {
const { slug } = params;
const category = url.searchParams.get('category');
const page = url.searchParams.get('page') || '1';
return {
slug,
category,
page: parseInt(page)
};
}
三、服务端数据加载 #
3.1 +page.server.ts #
typescript
export async function load({ fetch, cookies }) {
const token = cookies.get('token');
const response = await fetch('/api/user', {
headers: {
Authorization: `Bearer ${token}`
}
});
const user = await response.json();
return { user };
}
3.2 访问私有资源 #
typescript
import { DATABASE } from '$lib/server/database';
export async function load({ params }) {
const post = await DATABASE.posts.findUnique({
where: { slug: params.slug }
});
return { post };
}
3.3 Cookies 操作 #
typescript
export async function load({ cookies }) {
const sessionId = cookies.get('session_id');
if (sessionId) {
const session = await getSession(sessionId);
return { session };
}
return { session: null };
}
四、布局数据 #
4.1 根布局数据 #
src/routes/+layout.ts:
typescript
export async function load() {
return {
siteName: 'My Blog',
navigation: [
{ title: 'Home', href: '/' },
{ title: 'Blog', href: '/blog' },
{ title: 'About', href: '/about' }
]
};
}
src/routes/+layout.svelte:
svelte
<script>
let { children, data } = $props();
</script>
<header>
<h1>{data.siteName}</h1>
<nav>
{#each data.navigation as item}
<a href={item.href}>{item.title}</a>
{/each}
</nav>
</header>
<main>
{@render children()}
</main>
4.2 访问父布局数据 #
src/routes/blog/+page.ts:
typescript
export async function load({ parent }) {
const parentData = await parent();
console.log(parentData.siteName);
return {
posts: await getPosts()
};
}
4.3 数据合并 #
text
+layout.ts → { siteName: 'My Blog' }
+layout.server.ts → { user: { name: 'Alice' } }
+page.ts → { posts: [...] }
+page.server.ts → { settings: {...} }
最终 data = {
siteName: 'My Blog',
user: { name: 'Alice' },
posts: [...],
settings: {...}
}
五、数据依赖 #
5.1 depends 函数 #
typescript
export async function load({ fetch, depends }) {
depends('data:posts');
const response = await fetch('/api/posts');
const posts = await response.json();
return { posts };
}
5.2 手动刷新 #
svelte
<script>
import { invalidate } from '$app/navigation';
let { data } = $props();
function refresh() {
invalidate('data:posts');
}
</script>
<button onclick={refresh}>Refresh</button>
{#each data.posts as post}
<p>{post.title}</p>
{/each}
5.3 URL 依赖 #
typescript
export async function load({ url, depends }) {
depends(url.pathname);
const response = await fetch(`/api/page${url.pathname}`);
const data = await response.json();
return { data };
}
六、错误处理 #
6.1 抛出错误 #
typescript
import { error } from '@sveltejs/kit';
export async function load({ params }) {
const post = await getPost(params.slug);
if (!post) {
throw error(404, 'Post not found');
}
return { post };
}
6.2 错误类型 #
typescript
import { error } from '@sveltejs/kit';
export async function load() {
// 400 Bad Request
throw error(400, 'Invalid request');
// 401 Unauthorized
throw error(401, 'Not authenticated');
// 403 Forbidden
throw error(403, 'Access denied');
// 404 Not Found
throw error(404, 'Resource not found');
// 500 Internal Server Error
throw error(500, 'Server error');
}
6.3 自定义错误数据 #
typescript
import { error } from '@sveltejs/kit';
export async function load({ params }) {
const post = await getPost(params.slug);
if (!post) {
throw error(404, {
message: 'Post not found',
suggestion: 'Check the URL or browse our posts'
});
}
return { post };
}
七、重定向 #
7.1 基本重定向 #
typescript
import { redirect } from '@sveltejs/kit';
export async function load({ cookies }) {
const session = cookies.get('session');
if (!session) {
throw redirect(302, '/login');
}
return { session };
}
7.2 条件重定向 #
typescript
import { redirect } from '@sveltejs/kit';
export async function load({ url, cookies }) {
const session = cookies.get('session');
if (!session && url.pathname !== '/login') {
throw redirect(302, `/login?redirect=${url.pathname}`);
}
return { session };
}
7.3 永久重定向 #
typescript
import { redirect } from '@sveltejs/kit';
export async function load() {
throw redirect(301, '/new-location');
}
八、并行加载 #
8.1 Promise.all #
typescript
export async function load({ fetch }) {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { users, posts, comments };
}
8.2 条件加载 #
typescript
export async function load({ url, fetch }) {
const category = url.searchParams.get('category');
const page = url.searchParams.get('page') || '1';
const params = new URLSearchParams({ page });
if (category) params.append('category', category);
const response = await fetch(`/api/posts?${params}`);
const posts = await response.json();
return { posts, category, page: parseInt(page) };
}
九、完整示例 #
9.1 博客文章页面 #
src/routes/blog/[slug]/+page.server.ts:
typescript
import { error } from '@sveltejs/kit';
import { db } from '$lib/server/database';
export async function load({ params }) {
const post = await db.posts.findUnique({
where: { slug: params.slug },
include: {
author: true,
comments: {
include: { author: true },
orderBy: { createdAt: 'desc' }
}
}
});
if (!post) {
throw error(404, 'Post not found');
}
return { post };
}
src/routes/blog/[slug]/+page.svelte:
svelte
<script>
let { data } = $props();
</script>
<article>
<header>
<h1>{data.post.title}</h1>
<p>
By {data.post.author.name} •
{new Date(data.post.createdAt).toLocaleDateString()}
</p>
</header>
<div class="content">
{data.post.content}
</div>
<section class="comments">
<h2>Comments ({data.post.comments.length})</h2>
{#each data.post.comments as comment}
<div class="comment">
<strong>{comment.author.name}</strong>
<p>{comment.content}</p>
</div>
{/each}
</section>
</article>
9.2 分页数据 #
src/routes/blog/+page.ts:
typescript
export async function load({ url, fetch }) {
const page = parseInt(url.searchParams.get('page') || '1');
const limit = 10;
const response = await fetch(`/api/posts?page=${page}&limit=${limit}`);
const { posts, total } = await response.json();
return {
posts,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
};
}
src/routes/blog/+page.svelte:
svelte
<script>
let { data } = $props();
</script>
<h1>Blog Posts</h1>
{#each data.posts as post}
<article>
<h2><a href="/blog/{post.slug}">{post.title}</a></h2>
<p>{post.excerpt}</p>
</article>
{/each}
<div class="pagination">
{#if data.pagination.page > 1}
<a href="?page={data.pagination.page - 1}">Previous</a>
{/if}
<span>Page {data.pagination.page} of {data.pagination.totalPages}</span>
{#if data.pagination.page < data.pagination.totalPages}
<a href="?page={data.pagination.page + 1}">Next</a>
{/if}
</div>
十、总结 #
| 文件 | 执行环境 | 用途 |
|---|---|---|
+page.ts |
客户端+服务端 | 公共数据加载 |
+page.server.ts |
仅服务端 | 私有数据、数据库访问 |
+layout.ts |
客户端+服务端 | 布局数据 |
+layout.server.ts |
仅服务端 | 布局私有数据 |
数据加载要点:
load函数在渲染前执行+page.server.ts仅在服务端运行- 使用
parent()访问父布局数据 - 使用
depends()声明数据依赖 - 使用
error()抛出错误 - 使用
redirect()重定向
最后更新:2026-03-28