Svelte JS 学习笔记(三):基础Svelte Kit
Gemini 3.0 Pro的阅读建议,仅供参考
阅读建议
这份笔记梳理得非常清晰!你正在以惊人的速度吞噬 SvelteKit 的知识图谱。
不过,作为你的 Wails + Go 战略顾问,我有责任在你进入这一章之前,再次为你佩戴一副 “过滤眼镜”。
因为接下来的这几个章节(Headers, Cookies, Forms, API Routes),是 SvelteKit 作为“全栈框架” 的核心能力,但其中有 60% 的内容 是 Wails 开发者完全不需要(甚至不能用)的,而另外 40% 对你目前的 Gin 作业又非常有帮助。
为了防止你“走火入魔”去学一些 Wails 里用不到的屠龙技,我为你做了一个**“学习策略分级”**:
🟢 第一梯队:必须掌握(为了代码整洁)
1. 共享模块 (Shared Modules - $lib alias)
你的翻译: 库别名。
核心概念: SvelteKit 默认配置了一个别名
$lib指向src/lib文件夹。Go 类比: 这就像 Go 的
go.mod让你不用写../../pkg/util,而是直接写github.com/my/project/pkg/util。Wails 实战: 必用! 以后你在 Wails 里写组件,引入公共组件时写
import Button from '$lib/components/Button.svelte',比写../../../Button.svelte优雅一万倍。
2. 实例 ($app/state 或 $app/stores)
核心概念: 获取当前页面的 URL、参数、导航状态。
Go 类比: 类似于 Gin 的
c.Request.URL。Wails 实战: 常用。 虽然 Wails 不依赖 URL 路由,但有时候我们会用 URL 参数来做一些状态标记,或者监听
navigating状态来显示全局 Loading 条。
🟡 第二梯队:借鉴思想(为了 Gin + Svelte 作业)
这一部分在 Wails 里用不到,但在你马上要做的 Gin + Svelte 通讯录/购物网站 里很有参考价值。
1. 请求头和 Cookies (Headers and Cookies)
SvelteKit 做法: 在
load函数里读写 Cookies。Gin + Svelte 做法:
你不需要用 SvelteKit 的方式去读写 Cookie(那是服务端渲染的事)。
客户端: 你用 JS 的
document.cookie或者fetch自动携带 Cookie。服务端: 你用 Go Gin 的
c.SetCookie。
建议: 这一节看看就行,理解 HTTP 协议层面的 Cookie 机制,但别背 API,因为你用的是 Gin。
2. API 路由 (API Routes)
SvelteKit 做法: 在
+server.js里写GET,POST函数,模拟后端。你的现状: 你有真后端(Go)!
建议: 这一章你可以完全跳过代码实现,只看它关于 HTTP 方法(GET/POST/DELETE)的设计理念。你的 API 路由全是写在
main.go里的,不需要在 SvelteKit 里再造一层。
🔴 第三梯队:Wails 里的“禁区”(千万别沉迷)
这一部分是 SvelteKit 的精髓,但却是 Wails 的毒药。
1. 表单 (Forms) - 尤其是 Form Actions
这是 SvelteKit 最引以为傲的功能:它允许你写 <form action="?/login">,然后在后端无需 API 就能处理数据,甚至支持在该死的 No-JS 环境 下运行。
你的翻译修正:
Progressive Enhancement(渐进式增强) —— 指的是“即使浏览器禁用了 JS,网页也能通过原生 HTML 表单提交”。Wails 视角: 完全没用!
Wails 本身就是个 JS 容器,怎么可能禁用 JS?
Wails 通信走的是 IPC,不是 HTTP Form 提交。
切记: 在 Wails 里,永远使用
onclick事件 +go.App.Login()方法。千万别用<form action="...">。
2. 中间件 (Handlers / Hooks)
你的翻译修正:
Get Handlers在这里更准确的翻译是 “GET 处理函数” 或 “GET 端点”。在 Web 开发中,Middleware (中间件) 通常指拦截器,而 Handler 指最终干活的人。Wails 视角: Wails 没有 HTTP 请求链,所以 SvelteKit 的
hooks.server.js(请求拦截钩子)在 Wails 里是不存在的。
总结:你的“速通”指南
当你阅读这一章时,请保持这种**“上帝视角”**:
Headers/Cookies -> 看一眼,复习一下 HTTP 协议,联想一下 Gin 怎么写。
$lib -> 死记硬背,以后天天用。
Forms (Form Actions) -> 扫一眼原理,知道 SvelteKit 很强,然后立刻忘掉它。你以后的表单提交全是
JSON.stringify或者 Wails 绑定,不会用到原生 Form Action。API Routes -> 跳过。心中默念:“我有 Gin,我有 Gin,我有 Gin。”
带着这个过滤漏斗去看文档,你的效率会翻倍,而且不会被那些针对 Node.js 全栈开发的特性带偏。继续吧!
引言
Svelte Kit是什么
Svelte是一个组件框架 ,而Svelte Kit是一个APP框架 (或元框架 ,取决于你问谁),旨在解决生产前线产品的一些棘手的问题:
- 路由
- SSR
- 数据抓取
- TS整合
- 预渲染
- SPA
- 库打包
- 优化生产构建
- 部署到不同的主机上
- balabala……
SvelteKit程序默认是SSR的(就像传统MPA页面一样),首次加载性能优异,也很适合SEO;但也可以摇身一变,作为客户端导航(SPA),避免在用户跳转到其他页面时频繁重新加载页面页面上的东西(特别是第三方分析代码之类的东西)。虽说Svelte组件能跑的地方JS也能跑——但我们也很快会发现——用户也许根本不需要执行所有JS代码
也许这听起来复杂了一些,但不要担心:SvelteKit是成长型的框架,它会与你共同进步!先写个Hello World,然后慢慢把它雕刻成你心目中的Master Piece
项目结构

交互式教程在我这边跑不起来,所以这里手动创建项目了
如果你之前做过NodeJS项目,你会对package.json很熟悉。其中记录了项目的依赖——包括svelte和@sveltejs/kit——以及一大堆用来和SvelteKit CLI交互的库(我们现在正在页面底部的窗口 里允许npm run dev)
{
"name": "my-app",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/kit": "^2.47.1",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"svelte": "^5.41.0",
"svelte-check": "^4.3.3",
"typescript": "^5.9.3",
"vite": "^7.1.10"
}
}提示
package.json也指定了"type": "module",这意味着.js文件默认视为JS模块,而不是历史遗留的CommonJS格式(legacy CommonJS format)
相关信息
legacy CommonJS format 是 Node.js 早期使用的模块系统格式,即使用 require() 和 module.exports 来导入和导出模块的方式。随着 JavaScript 的发展,ES Modules(ESM)成为现代标准,使用 import 和 export 语法,具有静态分析、更好的 tree-shaking 支持等优势。
SvelteKit 项目默认采用 ES Modules,即在 package.json 中设置 "type": "module",这意味着项目中的 .js 文件会被视为 ES 模块。如果项目中仍需使用旧的 CommonJS 模块,需要将文件扩展名改为 .cjs,以明确标识其为 CommonJS 格式,避免模块系统混淆
svelte.config.js包含项目配置。现在不需要知道里面都有什么,但如果你好奇,请自行查阅文档
vite.config.js包含Vite配置。因为SvelteKit使用Vite打包,所以可以在Svelte项目中使用Vite功能,比如热插拔模块、TS类型支持和静态资源处理等等
<!-- src/app.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>src是源代码目录。src/app.html是现在的主页页面(SvelteKit会自动替换其中的%sveltekit.head%和%sveltekit.body%),而src/routes则定义了程序的路由
static是静态资源目录,包含所有(包括favicon.png和robots.txt)应该在部署时被包含进去的静态资源
路由
页面
SvelteKit使用基于文件系统的路由机制,这意味着程序的路由行为取决于项目结构(directories in your codebase)src/routes中的每个+page.svelte都是用来创建程序页面的。在这个程序中我们只有一个页面——src/routes/+page.svelte(映射到/路径)。如果我们尝试跳转到/about,我们会得到404 Not Found响应
现在让我们加入第二个页面,在src/routes/about下创建+page.svelte文件,然后把内容复制过去并更新:

这样我们就可以在/和/about之间来回跳转了
提示
与传统MPA不同的时,跳转到/about再跳回来只会更新当前页面的内容,就像SPA一样。这打开了两个新天地——首先是快速的SSR冷启动体验,其次是即时导航(这一行为可被配置)
布局 (Layouts)
程序里的不同路由有时需要共享同一套UI。我们不需要在每个路由目录下的+page.svelte里都重复同一套代码,我们可以使用+layout.svelte,将一套样式应用到当前目录下的所有路由中
在目前的项目中,我们有两个路由src/routes/+page.svelte 和 src/routes/about/+page.svelte,它们包含同一套导航栏和标题,现在创建一个新文件src/routes/+layout.svelte
然后把重复的代码从+page.svelte中移到+layout.svelte中。{@render children()}就是渲染页面内容的地方:
<script lang="ts">
import favicon from '$lib/assets/favicon.svg';
let { children } = $props();
</script>
<!--
<svelte:head>
<link rel="icon" href={favicon} />
</svelte:head>
-->
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
{@render children()}本来就有的代码被我注释起来了
+layout.svelte文件中的样式会被应用到所有子路由中,包括它们的旁路由(sibling +page.svelte)
我们可以在任意深度的路由中嵌套布局
路由参数
要创建动态路由,使用[]中括号包裹一个命名有效的变量,例如src/routes/blog/[slug]/+page.svelte可以匹配/blog/one,/blog/two,/blog/three之类的路由:
让我们去创建文件吧:
提示
一个URL段中可以有多个路由参数,只要它们是显式定义成多个参数的就行:foo/[bar]x[baz]就是一个有效的路由,其中的bar和baz都是有效的动态参数
数据加载
页面数据
SvelteKit本质上就是在做三件事:
- 路由——判断新请求匹配哪个路径
- 加载——获取路由需要的数据
- 渲染——(服务端)生成HTML页面和更新DOM(在浏览器内)
我们已经知道路由和SSR是如何工作的了,现在让我们看看中间这一部分——加载
Svelte程序的每一个页面都可以在+page.server.js文件(与+page.svelte位于同一相对目录中)中声明一个load函数。从文件名可以看出,这个模块只能工作在服务端(包括给客户端导航)。让我们添加src/routes/blog/+page.server.js文件,以替换上一节中硬编码的路径
提示
为了演示方便,这里是从src/routes/blog/data.js中导入数据的。而在实际项目中,我们应该从数据库或CMS中获取数据。但就目前而言,我们还是会本地模拟数据
在src/routes/blog/+page.svelte中,我们将使用data props访问这一数据

现在让我们修改具体的博客页面:


注
神他妈……传送门……
欢迎来到光圈科技丰富学习中心
我们衷心希望您度过了一个美好的假期
你的样品已被处理,我们现在准备开始测试
尽管丰富学习中心的测试目标之一就是安全,但你在测试室左边看到的那个光圈科技高能光束,可能会造成测试人员的永久功能性受损,比如蒸发。请保重
惠特利:我在做笔记,这是巨大的成功!
最后还有一个细节需要完善——当用户访问不存在的博客页面时,我们应该返回一个404页面:

我们会在后面的章节学到更多有关异常处理的技能
布局数据 (Layout Data)
+layout.svelte会给其下的所有子路由创建同一套UI,而+layout.svelte.js也会给在其之下的所有子路由加载数据
想象一下,当我们要给博客页面添加一个侧边栏时,我们可以从src/routes/blog/[slug]/+page.server.js中导出load函数加载summaries数据,就像之前写src/routes/blog/+page.server.js那样,但这样会有点哆嗦
我们可以把src/routes/blog/+page.server.js改名成src/routes/blog/+layout.server.js。注意到/blog路由仍然可以工作,因为data.summaries还能用
然后创建侧边栏UI:

我也不知道为什么渲染出来变数字索引了
原来是少写一对花括号,把title变成了列表索引,而slug变成了列表元素本身
layout组件及任何在其之下的页面都会继承来自父模块+layout.server.js的data.summaries
当我们跳转到其他博文时,我们只需要加载博文页面自身的数据——布局数据仍然有效,更多信息参见实例无效化
【略】 请求头和Cookies
设置请求头
在load函数(对于表单动作、钩子和API路由来说同理,我们之后会看到)内,我们可以调用setHeader函数——不出意外,我们也可以用它来设置响应头
常见情况是,我们有时需要修改Cache-Control响应头以自定义缓存行为,但出于演示目的,我们在这里会演示setHeader一个不那么推荐但更戏剧性的用途:
可能得刷新才能看到效果
读取和设置Cookies
setHeanders函数无法设置Set-Cookie响应头,你需要cookiesAPI:
- 设置Cookie:
cookies.set(name, value, options)
- 读取Cookie:
cookies.get(name, options);
共享模块
库别名 ($lib alias)
由于SvelteKit使用基于文件系统的路由机制,我们可以随手把各个路由需要的模块和组件放在相应目录下,正所谓:SV组件是块砖,哪里需要搬哪里 (put code close to where it’s used)
有时候,同一套代码会在多处被调用,这时最好把这些代码统一放在某处地方供所有人调用,而不是到处../../../../目录穿越导入代码。在SvelteKIt中,这个约定俗成的目录就是src/lib目录
从该目录导入库时,使用$lib全局变量作为前缀,比如对于src/lib/message.js,导入路径就是$lib/message.js