很多框架讲“表单提交”,默认思路是:
阻止默认提交
自己收集数据
自己 fetch
自己处理 loading/error
自己跳转SvelteKit 的气质不是这样。
它更像一座真正的剧院票务窗口:
观众提交表单
窗口直接受理
失败就把退回理由写在单子上
成功就给新票据或引导去下一站这就是 form actions。
它建立在很朴素的 Web 标准之上:
- 原生
<form> - 原生
POST - 服务端
actions - 可选的
use:enhance做渐进增强
这点非常关键:
先让它在“没有 JavaScript”的情况下也成立,再考虑增强体验。
这就是 SvelteKit 的剧场哲学:
先保证演出能演,再让机关更丝滑。
看一个完整例子。
// src/routes/box-office/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
import { db } from '$lib/server/db';
export const actions: Actions = {
default: async ({ request, locals }) => {
if (!locals.user) {
redirect(303, '/login');
}
const formData = await request.formData();
const playSlug = String(formData.get('playSlug') ?? '').trim();
const buyerName = String(formData.get('buyerName') ?? '').trim();
const seats = Number(formData.get('seats') ?? 0);
const errors: Record<string, string> = {};
if (!playSlug) errors.playSlug = '请选择剧目';
if (!buyerName) errors.buyerName = '请输入订票人姓名';
if (!Number.isInteger(seats) || seats < 1) errors.seats = '至少订 1 张票';
if (Object.keys(errors).length > 0) {
return fail(400, {
errors,
values: { playSlug, buyerName, seats }
});
}
const order = await db.ticketOrder.create({
data: {
playSlug,
buyerName,
seats,
userId: locals.user.id
}
});
redirect(303, `/orders/${order.id}`);
}
};<!-- src/routes/box-office/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import type { PageProps } from './$types';
let { form }: PageProps = $props();
let pending = $state(false);
</script>
<h1>票务窗口</h1>
<form
method="POST"
use:enhance={() => {
pending = true;
return async () => {
pending = false;
};
}}
>
<label>
剧目代号
<input name="playSlug" value={form?.values?.playSlug ?? ''} />
</label>
{#if form?.errors?.playSlug}
<p class="error">{form.errors.playSlug}</p>
{/if}
<label>
订票人
<input name="buyerName" value={form?.values?.buyerName ?? ''} />
</label>
{#if form?.errors?.buyerName}
<p class="error">{form.errors.buyerName}</p>
{/if}
<label>
票数
<input name="seats" type="number" min="1" value={form?.values?.seats ?? 1} />
</label>
{#if form?.errors?.seats}
<p class="error">{form.errors.seats}</p>
{/if}
<button disabled={pending}>
{pending ? '出票中...' : '确认订票'}
</button>
</form>这套模式的好处非常大:
- 没有 JS 也能提交
- 服务端校验天然可靠
fail(...)返回的字段错误会回到同一页redirect(...)是请求级跳转,不是你手搓的导航逻辑use:enhance只是增强体验,不是业务成立的前提
这比“手写一堆 fetch 提交流程”更稳、更贴近 Web,也更适合表单密集型系统。