前言
Svelte,一个语法简洁、入门容易,面向未来的前端框架。
从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1:
Svelte 以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非常适合构建轻量级 Web 项目。
为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。
如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!
欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。
表单 actions
+page.server.js
文件可以导出 actions,允许您使用 元素向服务端
POST
数据。
使用 时,客户端 JavaScript 是可选的,但您可以轻松地使用 JavaScript 渐进式增强 表单交互,以提供最佳的用户体验。
默认 action
在最简单的情况下,一个页面声明一个 default
action:
/// file: src/routes/login/+page.server.js
/** @satisfies {import('./$types').Actions} */
export const actions = {
default: async (event) => {
// TODO log the user in
}
};
要从 /login
页面调用此 action,只需添加一个 —— 不需要 JavaScript:
如果有人点击按钮,浏览器将通过 POST
请求将表单数据发送到服务端,运行默认 action。
[!NOTE] action 总是使用POST
请求,因为GET
请求不应该有副作用。
我们还可以通过添加 action
属性,调用来自其他页面的 action (例如,如果根布局中的导航栏有一个登录小部件):
/// file: src/routes/+layout.svelte
命名 actions
页面可以根据需要拥有多个命名 action ,而不是只有一个 default
action:
/// file: src/routes/login/+page.server.js
/** @satisfies {import('./$types').Actions} */
export const actions = {
--- default: async (event) => {---
+++ login: async (event) => {+++
// TODO log the user in
},
+++ register: async (event) => {
// TODO register the user
}+++
};
要调用命名 action ,添加一个以 /
字符为前缀的查询参数:
除了 action
属性,我们还可以在按钮上使用 formaction
属性,将相同的表单数据 POST
到与父 不同的 action :
/// file: src/routes/login/+page.svelte
[!NOTE] 我们不能在命名 action 旁边有默认 action ,因为如果您在没有重定向的情况下POST
到命名 action ,查询参数会保留在 URL 中,这意味着下一个默认POST
将通过之前的命名 action 进行处理。
action 的结构
每个 action 接收一个 RequestEvent
对象,允许您使用 request.formData()
读取数据。在处理请求之后(例如,通过设置 cookie 让用户登录),action 可以响应数据,这些数据将在对应页面的 form
属性以及整个应用范围的 page.form
中可用,直到下一次更新。
/// file: src/routes/login/+page.server.js
// @filename: ambient.d.ts
declare module '$lib/server/db';
// @filename: index.js
// ---cut---
import * as db from '$lib/server/db';
/** @type {import('./$types').PageServerLoad} */
export async function load({ cookies }) {
const user = await db.getUserFromSession(cookies.get('sessionid'));
return { user };
}
/** @satisfies {import('./$types').Actions} */
export const actions = {
login: async ({ cookies, request }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
const user = await db.getUser(email);
cookies.set('sessionid', await db.createSession(user), { path: '/' });
return { success: true };
},
register: async (event) => {
// TODO register the user
}
};
{#if form?.success}
Successfully logged in! Welcome back, {data.user.name}
{/if}
[!LEGACY]
在 Svelte 4 中,您将使用export let data
和export let form
来声明属性
验证错误
如果请求因数据无效而无法处理,您可以将验证错误 —— 以及之前提交的表单值 —— 返回给用户,以便他们可以重试。fail
函数允许您返回一个 HTTP 状态码(通常是 400 或 422,用于验证错误)以及数据。状态码可以通过 page.status
获取,数据可以通过 form
获取:
/// file: src/routes/login/+page.server.js
// @filename: ambient.d.ts
declare module '$lib/server/db';
// @filename: index.js
// ---cut---
+++import { fail } from '@sveltejs/kit';+++
import * as db from '$lib/server/db';
/** @satisfies {import('./$types').Actions} */
export const actions = {
login: async ({ cookies, request }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
+++ if (!email) {
return fail(400, { email, missing: true });
}+++
const user = await db.getUser(email);
+++ if (!user || user.password !== db.hash(password)) {
return fail(400, { email, incorrect: true });
}+++
cookies.set('sessionid', await db.createSession(user), { path: '/' });
return { success: true };
},
register: async (event) => {
// TODO register the user
}
};
[!NOTE] 请注意,作为预防措施,我们只将电子邮件返回给页面 —— 而不是密码。
/// file: src/routes/login/+page.svelte
返回的数据必须可序列化为 JSON。除此之外,结构完全由您决定。例如,如果页面上有多个表单,您可以使用 id
属性或类似的方式区分返回的 form
数据对应哪个 。
重定向
重定向(和错误)与 load
中的工作方式完全相同:
// @errors: 2345
/// file: src/routes/login/+page.server.js
// @filename: ambient.d.ts
declare module '$lib/server/db';
// @filename: index.js
// ---cut---
import { fail, +++redirect+++ } from '@sveltejs/kit';
import * as db from '$lib/server/db';
/** @satisfies {import('./$types').Actions} */
export const actions = {
login: async ({ cookies, request, +++url+++ }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
const user = await db.getUser(email);
if (!user) {
return fail(400, { email, missing: true });
}
if (user.password !== db.hash(password)) {
return fail(400, { email, incorrect: true });
}
cookies.set('sessionid', await db.createSession(user), { path: '/' });
+++ if (url.searchParams.has('redirectTo')) {
redirect(303, url.searchParams.get('redirectTo'));
}+++
return { success: true };
},
register: async (event) => {
// TODO register the user
}
};
加载数据
action 运行后,页面将重新渲染(除非发生重定向或意外错误), action 的返回值将作为 form
属性提供给页面。这意味着页面的 load
函数将在 action 完成后运行。
请注意,handle
在 action 被调用之前运行,并且不会在 load
函数之前重新运行。这意味着,例如,如果您使用 handle
根据 cookie 填充 event.locals
,则在 action 中设置或删除 cookie 时,必须更新 event.locals
:
/// file: src/hooks.server.js
// @filename: ambient.d.ts
declare namespace App {
interface Locals {
user: {
name: string;
} | null
}
}
// @filename: global.d.ts
declare global {
function getUser(sessionid: string | undefined): {
name: string;
};
}
export {};
// @filename: index.js
// ---cut---
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
event.locals.user = await getUser(event.cookies.get('sessionid'));
return resolve(event);
}
/// file: src/routes/account/+page.server.js
// @filename: ambient.d.ts
declare namespace App {
interface Locals {
user: {
name: string;
} | null
}
}
// @filename: index.js
// ---cut---
/** @type {import('./$types').PageServerLoad} */
export function load(event) {
return {
user: event.locals.user
};
}
/** @satisfies {import('./$types').Actions} */
export const actions = {
logout: async (event) => {
event.cookies.delete('sessionid', { path: '/' });
event.locals.user = null;
}
};
渐进式增强
在前面的章节中,我们构建了一个在没有客户端 JavaScript 的情况下工作的 /login
action —— 没有 fetch
。这很好,但当 JavaScript 可用 时,我们可以渐进式增强表单交互,以提供更好的用户体验。
use:enhance
渐进式增强表单的最简单方法是添加 use:enhance
action :
/// file: src/routes/login/+page.svelte
[!NOTE]
use:enhance
只能与method="POST"
的表单一起使用。它将无法与method="GET"
一起工作,后者是未指定方法的表单的默认方法。在未指定method="POST"
的表单上尝试使用use:enhance
将导致错误。[!NOTE] 是的,
enhance
action 和都叫做 'action',这些文档充满了各种 action。抱歉。
没有参数时,use:enhance
将模拟浏览器原生行为,只是不进行完整页面重载。它将:
- 在成功或无效响应时更新
form
属性、page.form
和page.status
,但仅当 action 在您提交的同一页面上时。例如,如果您的表单看起来像,
form
属性和page.form
状态将 不会 更新。这是因为在本地表单提交的情况下,您将被重定向到 action 所在的页面。如果您希望无论如何都能更新,使用applyAction
- 重置
元素
- 在成功响应时使用
invalidateAll
使所有数据失效 - 在重定向响应时调用
goto
- 如果发生错误,渲染最近的
+error
边界 - 将焦点重置到适当的元素
自定义 use:enhance
要自定义行为,您可以提供一个 SubmitFunction
,它会在表单提交前立即运行,并(可选地)返回一个随 ActionResult
一起运行的回调。请注意,如果您返回一个回调,上述默认行为将不会被触发。要恢复默认行为,请调用 update
。
您可以使用这些函数来显示和隐藏加载界面等。
如果您返回一个回调,您可能需要重现部分默认的 use:enhance
行为,但在成功响应时不使所有数据失效。您可以使用 applyAction
来实现:
/// file: src/routes/login/+page.svelte
applyAction(result)
的行为取决于 result.type
:
success
,failure
— 将page.status
设置为result.status
,并将form
和page.form
更新为result.data
(无论您从哪里提交,这与enhance
的update
形成对比)redirect
— 调用goto(result.location, { invalidateAll: true })
error
— 使用result.error
渲染最近的+error
边界
在所有情况下,焦点将被重置。
自定义事件监听器
我们也可以不使用 use:enhance
,在 上使用普通的事件监听器,自己实现渐进式增强:
请注意,在使用 $app/forms
中相应的方法进一步处理响应之前,需要 deserialize
响应。仅 JSON.parse()
是不够的,因为表单 action(如 load
函数)也支持返回 Date
或 BigInt
对象。
如果您在 +page.server.js
旁边有一个 +server.js
,fetch
请求将默认路由到那里。要改为 POST
到 +page.server.js
中的 action ,请使用自定义的 x-sveltekit-action
头:
const response = await fetch(this.action, {
method: 'POST',
body: data,
+++ headers: {
'x-sveltekit-action': 'true'
}+++
});
替代方案
表单 action 是向服务端发送数据的首选方法,因为它们可以渐进式增强,但您也可以使用 +server.js
文件来公开(例如)一个 JSON API。以下是这种交互的示例:
// @errors: 2355 1360 2322
/// file: src/routes/api/ci/+server.js
/** @type {import('./$types').RequestHandler} */
export function POST() {
// do something
}
GET 与 POST
如我们所见,要调用表单 action ,必须使用 method="POST"
。
有些表单不需要向服务端 POST
数据 —— 例如搜索输入。对于这些表单,您可以使用 method="GET"
(或等效地,不指定 method
),SvelteKit 将像处理 元素一样处理它们,使用客户端路由而不是完整页面导航:
提交此表单将导航到 /search?q=...
并调用您的 load
函数,但不会调用 action 。与 元素一样,您可以在
上设置
data-sveltekit-reload
、data-sveltekit-replacestate
、data-sveltekit-keepfocus
以及 data-sveltekit-noscroll
属性,以控制路由器的行为。
进一步阅读
Svelte 中文文档
点击查看中文文档 - SvelteKit 表单 actions。
系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!
此外我还写过 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答读者问等 14 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog
欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。