F FisherHub Docs

03. 表单与操作

SvelteKit 内置了强大的表单处理机制。通过 actions 可以在服务端处理表单提交,结合 use:enhance 实现无缝的渐进增强体验。

服务端 Actions

+page.server.ts 中的 actions 对象定义了表单处理函数。

// src/routes/contact/+page.server.ts
import type { Actions } from './$types';

export const actions: Actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const name = data.get('name') as string;
    const email = data.get('email') as string;
    const message = data.get('message') as string;

    // 表单验证
    const errors: Record<string, string> = {};

    if (!name || name.length < 2) {
      errors.name = '姓名至少 2 个字符';
    }

    if (!email || !email.includes('@')) {
      errors.email = '请输入有效的邮箱地址';
    }

    if (!message || message.length < 10) {
      errors.message = '消息至少 10 个字符';
    }

    if (Object.keys(errors).length > 0) {
      // 返回验证错误,SvelteKit 会自动序列化
      return { errors, values: { name, email, message } };
    }

    // 处理成功逻辑(如发送邮件、存入数据库)
    await sendContactEmail({ name, email, message });

    // 重定向到成功页
    return { success: true };
  }
};

对应的表单组件:

<!-- src/routes/contact/+page.svelte -->
<script lang="ts">
  import { enhance } from '$app/forms';
  import type { PageData } from './$types';

  let { data, form } = $props();
</script>

<h1>联系我们</h1>

{#if form?.success}
  <p class="success">消息已发送,我们会尽快回复!</p>
{:else}
  <form method="POST" use:enhance>
    <label>
      姓名
      <input name="name" value={form?.values?.name ?? ''} />
      {#if form?.errors?.name}
        <span class="error">{form.errors.name}</span>
      {/if}
    </label>

    <label>
      邮箱
      <input name="email" type="email" value={form?.values?.email ?? ''} />
      {#if form?.errors?.email}
        <span class="error">{form.errors.email}</span>
      {/if}
    </label>

    <label>
      消息
      <textarea name="message" rows="5">{form?.values?.message ?? ''}</textarea>
      {#if form?.errors?.message}
        <span class="error">{form.errors.message}</span>
      {/if}
    </label>

    <button type="submit">发送</button>
  </form>
{/if}

Named Actions

除了 default 外,可以定义命名 action 来处理不同操作:

export const actions: Actions = {
  login: async ({ request, cookies }) => {
    const data = await request.formData();
    // 登录逻辑...
  },
  register: async ({ request, cookies }) => {
    const data = await request.formData();
    // 注册逻辑...
  },
  logout: async ({ cookies }) => {
    cookies.delete('session', { path: '/' });
  }
};

前端通过表单的 action 属性区分:

<form method="POST" action="?/login" use:enhance>
  <!-- 登录字段 -->
</form>

<form method="POST" action="?/register" use:enhance>
  <!-- 注册字段 -->
</form>

use:enhance 渐进增强

use:enhance 是 SvelteKit 提供的一个 action,让传统表单提交获得 SPA 般的体验:

  • 表单提交不刷新页面
  • 网络请求通过 fetch 发送
  • 服务器返回的数据自动更新 form 属性
  • 提交期间可显示加载状态
<script lang="ts">
  import { enhance } from '$app/forms';
</script>

<form
  method="POST"
  use:enhance={({ form, data, action, cancel }) => {
    // 提交前回调:可在此显示加载状态
    document.getElementById('submit-btn')!.disabled = true;

    return async ({ result, update }) => {
      // result.type 可能是 'success' | 'error' | 'redirect'
      if (result.type === 'success') {
        // 手动调用 update() 更新 form 数据
        update();
        // 重置表单
        form.reset();
      }
      document.getElementById('submit-btn')!.disabled = false;
    };
  }}
>
  <input name="query" />
  <button id="submit-btn" type="submit">搜索</button>
</form>

API 端点 (+server.ts)

除了页面路由,SvelteKit 还支持纯 API 端点。在路由目录下创建 +server.ts,导出 HTTP 方法处理函数。

// src/routes/api/posts/[slug]/+server.ts
import type { RequestHandler } from './$types';
import { json, error } from '@sveltejs/kit';

export const GET: RequestHandler = async ({ params, url }) => {
  const slug = params.slug;
  const post = await getPost(slug);

  if (!post) {
    throw error(404, '文章不存在');
  }

  return json(post);
};

export const POST: RequestHandler = async ({ request, params }) => {
  const body = await request.json();
  const slug = params.slug;

  // 更新文章
  await updatePost(slug, body);

  return json({ success: true });
};

export const DELETE: RequestHandler = async ({ params }) => {
  await deletePost(params.slug);
  return new Response(null, { status: 204 });
};

接收 JSON 请求

通过 request.json() 解析请求体,配合前端 fetch 使用:

export const POST: RequestHandler = async ({ request }) => {
  const { title, content } = await request.json();

  if (!title || !content) {
    throw error(400, '标题和内容不能为空');
  }

  const post = await createPost({ title, content });

  return json(post, { status: 201 });
};

综合示例:Todo 列表

一个完整的 Todo 应用展示 actions 和 API 的结合:

+page.server.ts:

import type { Actions, PageServerLoad } from './$types';

let todos: Array<{ id: number; text: string; done: boolean }> = [];
let nextId = 1;

export const load: PageServerLoad = async () => {
  return { todos };
};

export const actions: Actions = {
  add: async ({ request }) => {
    const data = await request.formData();
    const text = data.get('text') as string;

    if (!text || text.length < 1) {
      return { error: '请输入待办事项' };
    }

    todos = [...todos, { id: nextId++, text, done: false }];
    return { todos };
  },
  toggle: async ({ request }) => {
    const data = await request.formData();
    const id = Number(data.get('id'));
    todos = todos.map(t => t.id === id ? { ...t, done: !t.done } : t);
    return { todos };
  }
};

+page.svelte:

<script lang="ts">
  import { enhance } from '$app/forms';
  let { data, form } = $props();
  let items = $derived(form?.todos ?? data.todos);
</script>

<h1>Todo 列表</h1>

<form method="POST" action="?/add" use:enhance>
  <input name="text" placeholder="新增待办..." />
  <button type="submit">添加</button>
</form>

<ul>
  {#each items as item}
    <li>
      <form method="POST" action="?/toggle" use:enhance>
        <input type="hidden" name="id" value={item.id} />
        <button type="submit">
          {item.done ? '☑' : '☐'} {item.text}
        </button>
      </form>
    </li>
  {/each}
</ul>

总结

本章你学会了:

  • 使用 +page.server.tsactions 处理表单提交
  • Named actions 处理不同表单类型
  • use:enhance 实现无刷新渐进增强
  • +server.ts 创建 RESTful API 端点
  • 完整表单验证和错误回显

下一章将介绍部署方案和进阶优化技巧。