MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

SvelteKit 路由入门:页面路由与 API 路由的基础使用

2024-01-287.2k 阅读

页面路由基础

在 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 方法,如 POSTPUTDELETE 等。以 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>版权所有 &copy; 2024</p>
    </footer>
</body>
</html>

在这个全局布局中,<slot> 会首先渲染 src/routes/products/__layout.svelte 的内容,然后再由 src/routes/products/__layout.svelte 中的 <slot> 渲染具体的产品列表页或产品详情页内容。通过这种嵌套布局的方式,可以构建出层次分明、结构清晰的应用界面。

路由的错误处理

在路由的使用过程中,错误处理是至关重要的,它可以提供更好的用户体验,并帮助开发者快速定位和解决问题。

页面路由的错误处理

在页面路由的 load 函数中,可以通过返回特定的 statuserror 来处理错误。例如,当请求的数据不存在时返回 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.priorityload.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 的路由机制有了较为深入的理解和掌握。在实际开发中,可以根据具体的需求灵活运用这些知识,构建出高效、稳定且功能丰富的前端应用。