SvelteKit 路由入门:页面路由与 API 路由的基础使用
页面路由基础
在 SvelteKit 中,页面路由是构建应用用户界面导航的核心机制。其设计理念基于文件系统,这使得路由的创建和管理变得直观和高效。
页面文件结构与路由映射
SvelteKit 通过约定的文件结构来定义路由。在 src/routes
目录下,每个 .svelte
文件对应一个路由。例如,创建 src/routes/home.svelte
文件,这将自动创建一个 /home
的路由。
<!-- src/routes/home.svelte -->
<script>
// 页面级别的脚本逻辑
</script>
<main>
<h1>这是首页</h1>
<p>欢迎来到应用的首页。</p>
</main>
上述代码展示了一个简单的首页路由组件。当用户访问 /home
路径时,这个组件将被渲染。
如果要创建嵌套路由,可以在 src/routes
目录下创建子目录。例如,src/routes/products
目录下的文件将对应 /products
及其子路由。src/routes/products/[id].svelte
文件中的 [id]
是一个动态参数,表示产品的唯一标识符。
<!-- src/routes/products/[id].svelte -->
<script context="module">
export async function load({ params }) {
const productId = params.id;
// 这里可以通过 productId 从 API 获取产品详细信息
const product = await fetch(`/api/products/${productId}`).then(res => res.json());
return {
props: {
product
}
};
}
</script>
<script>
export let product;
</script>
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
</main>
在这个例子中,load
函数是 SvelteKit 提供的加载数据的机制。它在组件渲染前执行,通过 params
获取动态参数 id
,然后从 API 获取产品详细信息,并将其作为 props
传递给组件。
路由导航
在 SvelteKit 应用中,实现页面之间的导航主要通过 <a>
标签或者 svelte:router
指令。使用 <a>
标签进行导航是最直观的方式,例如:
<!-- 在某个组件中 -->
<ul>
<li><a href="/home">首页</a></li>
<li><a href="/products">产品列表</a></li>
</ul>
然而,这种方式会导致页面的完全刷新。为了实现无刷新的导航,可以使用 svelte:router
指令,这需要引入 SvelteKit 的 navigate
函数。
<script>
import { navigate } from '$app/navigation';
</script>
<button on:click={() => navigate('/home')}>前往首页</button>
navigate
函数可以接收路径作为参数,并且支持一些额外的选项,如 replace
(用于替换当前历史记录而不是添加新的记录)等。
API 路由基础
除了页面路由,SvelteKit 还提供了强大的 API 路由功能,使得在前端应用中创建后端 API 变得轻而易举。这在开发全栈应用时尤为有用,能够将前端和后端逻辑紧密结合。
API 路由文件结构
API 路由同样基于文件系统,位于 src/routes/api
目录下。每个 .js
或 .ts
文件对应一个 API 端点。例如,创建 src/routes/api/products.js
文件将定义一个 /api/products
的 API 路由。
// src/routes/api/products.js
import { json } from '@sveltejs/kit';
// 模拟产品数据
const products = [
{ id: 1, name: '产品1', description: '这是产品1的描述' },
{ id: 2, name: '产品2', description: '这是产品2的描述' }
];
export async function GET() {
return json(products);
}
在上述代码中,GET
函数定义了处理 HTTP GET 请求的逻辑。json
函数来自 @sveltejs/kit
,用于将数据以 JSON 格式返回。这里简单地返回了一个模拟的产品列表。
如果要处理动态参数的 API 路由,可以使用类似页面路由的方式,例如 src/routes/api/products/[id].js
。
// src/routes/api/products/[id].js
import { json } from '@sveltejs/kit';
// 模拟产品数据
const products = [
{ id: 1, name: '产品1', description: '这是产品1的描述' },
{ id: 2, name: '产品2', description: '这是产品2的描述' }
];
export async function GET({ params }) {
const productId = parseInt(params.id);
const product = products.find(p => p.id === productId);
if (product) {
return json(product);
} else {
return new Response('未找到产品', { status: 404 });
}
}
在这个例子中,GET
函数通过 params
获取动态参数 id
,然后在产品列表中查找对应的产品。如果找到则返回产品数据,否则返回 404 状态码。
处理不同 HTTP 方法
API 路由可以处理多种 HTTP 方法,如 POST
、PUT
、DELETE
等。以 POST
方法为例,假设我们要创建一个添加新产品的 API。
// src/routes/api/products.js
import { json } from '@sveltejs/kit';
let products = [
{ id: 1, name: '产品1', description: '这是产品1的描述' },
{ id: 2, name: '产品2', description: '这是产品2的描述' }
];
export async function POST({ request }) {
const { name, description } = await request.json();
const newProduct = { id: products.length + 1, name, description };
products.push(newProduct);
return json(newProduct);
}
在上述代码中,POST
函数通过 request.json()
获取请求体中的数据,创建一个新的产品并添加到产品列表中,然后返回新创建的产品。
页面路由与 API 路由的交互
在实际应用开发中,页面路由和 API 路由紧密协作,共同为用户提供完整的功能体验。
页面从 API 获取数据
如前面产品详情页的例子所示,页面路由组件通过 load
函数从 API 路由获取数据。这使得页面可以在渲染前准备好所需的数据,提高用户体验。
<!-- src/routes/products/[id].svelte -->
<script context="module">
export async function load({ params }) {
const productId = params.id;
const response = await fetch(`/api/products/${productId}`);
if (response.ok) {
const product = await response.json();
return {
props: {
product
}
};
} else {
// 处理错误情况
return {
status: 404,
error: new Error('产品未找到')
};
}
}
</script>
<script>
export let product;
let error;
if ($page.error) {
error = $page.error;
}
</script>
<main>
{#if error}
<p>{error.message}</p>
{:else if product}
<h1>{product.name}</h1>
<p>{product.description}</p>
{/if}
</main>
在这个改进的代码中,load
函数不仅获取数据,还处理了可能出现的错误情况。如果 API 返回 404 等错误状态码,load
函数会返回相应的错误信息,组件可以根据这些信息显示友好的错误提示。
页面向 API 发送数据
页面也常常需要向 API 发送数据,比如提交表单。以添加产品的表单为例:
<!-- src/routes/products/new.svelte -->
<script>
let name = '';
let description = '';
async function submitForm() {
const response = await fetch('/api/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name, description })
});
if (response.ok) {
const newProduct = await response.json();
// 提交成功后的处理,例如导航到产品详情页
} else {
// 处理提交失败的情况
}
}
</script>
<main>
<h1>添加新产品</h1>
<form on:submit|preventDefault={submitForm}>
<label>名称:<input type="text" bind:value={name} /></label>
<label>描述:<input type="text" bind:value={description} /></label>
<button type="submit">提交</button>
</form>
</main>
在这个表单组件中,当用户点击提交按钮时,submitForm
函数会向 /api/products
发送一个 POST
请求,将表单数据发送到 API 进行处理。根据 API 的响应,组件可以进行不同的操作,如导航到新创建产品的详情页或显示错误信息。
路由参数与查询字符串
在 SvelteKit 的路由中,参数和查询字符串是传递数据的重要方式,它们在页面路由和 API 路由中都有着广泛的应用。
路由参数
我们前面已经提到了动态路由参数,如 [id]
。在页面路由和 API 路由中,获取和使用这些参数的方式基本相同。
在页面路由组件中,通过 load
函数的 params
获取参数:
<!-- src/routes/products/[id].svelte -->
<script context="module">
export async function load({ params }) {
const productId = params.id;
// 使用 productId 进行数据获取等操作
return {
props: {
productId
}
};
}
</script>
<script>
export let productId;
</script>
<main>
<p>产品 ID:{productId}</p>
</main>
在 API 路由中,同样通过 params
获取参数:
// src/routes/api/products/[id].js
import { json } from '@sveltejs/kit';
export async function GET({ params }) {
const productId = params.id;
// 根据 productId 返回相应的数据
return json({ productId });
}
路由参数的命名可以根据实际需求进行,只要遵循文件名的命名规范即可。
查询字符串
查询字符串是在 URL 中以 ?
开始,由键值对组成的部分,如 ?name=value&age=20
。在 SvelteKit 中,可以在 load
函数(页面路由)或路由处理函数(API 路由)中获取查询字符串。
在页面路由中:
<!-- src/routes/search.svelte -->
<script context="module">
export async function load({ url }) {
const query = url.searchParams;
const keyword = query.get('keyword');
return {
props: {
keyword
}
};
}
</script>
<script>
export let keyword;
</script>
<main>
{#if keyword}
<p>搜索关键词:{keyword}</p>
{:else}
<p>请输入搜索关键词</p>
{/if}
</main>
在上述代码中,通过 url.searchParams
获取查询字符串对象,然后使用 get
方法获取特定的参数值。
在 API 路由中:
// src/routes/api/search.js
import { json } from '@sveltejs/kit';
export async function GET({ url }) {
const query = url.searchParams;
const keyword = query.get('keyword');
// 根据 keyword 进行搜索逻辑
return json({ keyword });
}
查询字符串常用于传递一些可选的参数,如搜索关键词、排序方式等,为路由提供更灵活的数据交互方式。
路由嵌套与布局
SvelteKit 的路由嵌套和布局功能使得构建复杂的应用结构变得更加容易,同时保持代码的清晰和可维护性。
路由嵌套
我们前面提到了通过创建子目录来实现路由嵌套。例如,src/routes/products
目录下的文件对应 /products
及其子路由。在嵌套路由中,父路由和子路由之间存在一定的关系。
假设我们有一个产品列表页 /products
和产品详情页 /products/[id]
,并且产品列表页有一些公共的样式和逻辑。可以在 src/routes/products
目录下创建一个 __layout.svelte
文件。
<!-- src/routes/products/__layout.svelte -->
<script>
// 这里可以定义一些公共的脚本逻辑,如获取产品列表数据
</script>
<main>
<h1>产品相关页面</h1>
<!-- 子路由内容将在这里渲染 -->
<slot />
</main>
在这个布局文件中,<slot>
用于渲染子路由的内容。这样,无论是产品列表页还是产品详情页,都会包含 __layout.svelte
中的样式和结构。
布局嵌套
布局也可以进行嵌套。例如,我们可能有一个全局的布局 src/routes/__layout.svelte
,以及产品模块的布局 src/routes/products/__layout.svelte
。
<!-- src/routes/__layout.svelte -->
<script>
// 全局的脚本逻辑
</script>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>我的应用</title>
</head>
<body>
<header>
<nav>
<a href="/">首页</a>
<a href="/products">产品</a>
</nav>
</header>
<!-- 子布局或页面内容将在这里渲染 -->
<slot />
<footer>
<p>版权所有 © 2024</p>
</footer>
</body>
</html>
在这个全局布局中,<slot>
会首先渲染 src/routes/products/__layout.svelte
的内容,然后再由 src/routes/products/__layout.svelte
中的 <slot>
渲染具体的产品列表页或产品详情页内容。通过这种嵌套布局的方式,可以构建出层次分明、结构清晰的应用界面。
路由的错误处理
在路由的使用过程中,错误处理是至关重要的,它可以提供更好的用户体验,并帮助开发者快速定位和解决问题。
页面路由的错误处理
在页面路由的 load
函数中,可以通过返回特定的 status
和 error
来处理错误。例如,当请求的数据不存在时返回 404 错误。
<!-- src/routes/products/[id].svelte -->
<script context="module">
export async function load({ params }) {
const productId = params.id;
const response = await fetch(`/api/products/${productId}`);
if (response.ok) {
const product = await response.json();
return {
props: {
product
}
};
} else {
return {
status: 404,
error: new Error('产品未找到')
};
}
}
</script>
<script>
export let product;
let error;
if ($page.error) {
error = $page.error;
}
</script>
<main>
{#if error}
<p>{error.message}</p>
{:else if product}
<h1>{product.name}</h1>
<p>{product.description}</p>
{/if}
</main>
在组件中,可以通过 $page.error
获取 load
函数返回的错误信息,并根据情况显示相应的错误提示。
API 路由的错误处理
在 API 路由中,同样可以处理错误并返回合适的 HTTP 状态码。例如,当处理请求数据格式错误时返回 400 错误。
// src/routes/api/products.js
import { json } from '@sveltejs/kit';
export async function POST({ request }) {
try {
const { name, description } = await request.json();
const newProduct = { id: 1, name, description };
return json(newProduct);
} catch (error) {
return new Response('请求数据格式错误', { status: 400 });
}
}
在这个例子中,通过 try - catch
块捕获可能出现的错误,如解析 JSON 数据失败,然后返回带有 400 状态码的响应。
路由的高级应用
随着对 SvelteKit 路由的深入理解,我们可以探索一些高级应用场景,进一步提升应用的功能和性能。
动态路由加载
有时候,我们可能希望根据用户的操作或某些条件动态加载路由。SvelteKit 提供了 load
函数的异步特性来实现这一点。
假设我们有一个多语言的应用,不同语言的页面结构相同,但内容不同。我们可以根据用户选择的语言动态加载相应的页面数据。
<!-- src/routes/home.svelte -->
<script context="module">
export async function load({ url }) {
const lang = url.searchParams.get('lang') || 'en';
const response = await fetch(`/api/${lang}/home`);
if (response.ok) {
const data = await response.json();
return {
props: {
data
}
};
} else {
return {
status: 404,
error: new Error('未找到对应语言的内容')
};
}
}
</script>
<script>
export let data;
let error;
if ($page.error) {
error = $page.error;
}
</script>
<main>
{#if error}
<p>{error.message}</p>
{:else if data}
<h1>{data.title}</h1>
<p>{data.content}</p>
{/if}
</main>
在这个例子中,通过查询字符串获取用户选择的语言,然后根据语言动态加载相应的 API 数据,实现了动态路由加载的效果。
路由中间件
虽然 SvelteKit 没有像传统后端框架那样的中间件概念,但我们可以通过一些技巧实现类似的功能。例如,在处理 API 路由时,我们可能希望在每个请求前进行身份验证。
// src/routes/api/_middleware.js
import { getAuthToken } from '$lib/auth';
export async function handle({ event, resolve }) {
const token = event.request.headers.get('Authorization');
if (!token ||!getAuthToken(token)) {
return new Response('未授权', { status: 401 });
}
return resolve(event);
}
在上述代码中,handle
函数在每个 API 请求前执行,检查请求头中的 Authorization
字段,如果没有有效的令牌则返回 401 未授权错误,否则继续处理请求。通过这种方式,我们可以实现类似路由中间件的功能,对请求进行统一的处理和验证。
路由性能优化
在大型应用中,路由性能优化是非常重要的。SvelteKit 提供了一些机制来帮助我们实现这一点。
首先,合理使用 load
函数的缓存功能。load
函数默认会缓存数据,这意味着如果相同的路由参数和查询字符串再次请求,load
函数不会重复执行,而是直接使用缓存的数据。
<!-- src/routes/products/[id].svelte -->
<script context="module">
export const load = async ({ params }) => {
const productId = params.id;
const response = await fetch(`/api/products/${productId}`);
if (response.ok) {
const product = await response.json();
return {
props: {
product
}
};
} else {
return {
status: 404,
error: new Error('产品未找到')
};
}
};
// 设置缓存策略
load.priority = 1;
load.depends = ['product:' + params.id];
</script>
<script>
export let product;
let error;
if ($page.error) {
error = $page.error;
}
</script>
<main>
{#if error}
<p>{error.message}</p>
{:else if product}
<h1>{product.name}</h1>
<p>{product.description}</p>
{/if}
</main>
在这个例子中,通过设置 load.priority
和 load.depends
可以进一步优化缓存策略。priority
表示加载的优先级,depends
表示缓存依赖的键,当依赖的键发生变化时,缓存会失效,从而重新执行 load
函数。
另外,代码拆分也是提高路由性能的重要手段。SvelteKit 支持动态导入组件,这样可以将不常用的路由组件按需加载,而不是在应用启动时全部加载。
<!-- src/routes/settings.svelte -->
<script>
const SettingsComponent = () => import('./settings - components/AdvancedSettings.svelte');
</script>
<main>
<h1>设置页面</h1>
<Suspense>
<SettingsComponent />
</Suspense>
</main>
在上述代码中,SettingsComponent
是通过动态导入的方式引入的,只有当用户访问到设置页面时,相关的组件才会被加载,从而提高了应用的初始加载性能。
通过以上对 SvelteKit 页面路由与 API 路由的基础使用以及一些高级应用的介绍,相信你已经对 SvelteKit 的路由机制有了较为深入的理解和掌握。在实际开发中,可以根据具体的需求灵活运用这些知识,构建出高效、稳定且功能丰富的前端应用。