SvelteKit 最新中文文档教程(4)—— 表单 actions

news2025/3/18 7:52:39

前言

Svelte,一个语法简洁、入门容易,面向未来的前端框架。

从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1

image.png

Svelte 以其独特的编译时优化机制著称,具有轻量级高性能易上手等特性,非常适合构建轻量级 Web 项目

为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。

如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。

表单 actions

+page.server.js 文件可以导出 actions,允许您使用 <form> 元素向服务端 POST 数据。

使用 <form> 时,客户端 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,只需添加一个 <form> —— 不需要 JavaScript:

<!--- file: src/routes/login/+page.svelte --->
<form method="POST">
  <label>
    Email
    <input name="email" type="email">
  </label>
  <label>
    Password
    <input name="password" type="password">
  </label>
  <button>Log in</button>
</form>

如果有人点击按钮,浏览器将通过 POST 请求将表单数据发送到服务端,运行默认 action。

[!NOTE] action 总是使用 POST 请求,因为 GET 请求不应该有副作用。

我们还可以通过添加 action 属性,调用来自其他页面的 action (例如,如果根布局中的导航栏有一个登录小部件):

/// file: src/routes/+layout.svelte
<form method="POST" action="/login">
	<!-- content -->
</form>

命名 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 ,添加一个以 / 字符为前缀的查询参数:

<!--- file: src/routes/login/+page.svelte --->
<form method="POST" action="?/register">
<!--- file: src/routes/+layout.svelte --->
<form method="POST" action="/login?/register">

除了 action 属性,我们还可以在按钮上使用 formaction 属性,将相同的表单数据 POST 到与父 <form> 不同的 action :

/// file: src/routes/login/+page.svelte
<form method="POST" +++action="?/login"+++>
  <label>
    Email
    <input name="email" type="email">
  </label>
  <label>
    Password
    <input name="password" type="password">
  </label>
  <button>Log in</button>
  +++<button formaction="?/register">Register</button>+++
</form>

[!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
  }
};
<!--- file: src/routes/login/+page.svelte --->
<script>
  /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */
  let { data, form } = $props();
</script>

{#if form?.success}
  <!-- 这个消息是短暂的;它存在是因为页面是响应表单提交而渲染的。如果用户重新加载,消息将消失 -->
  <p>Successfully logged in! Welcome back, {data.user.name}</p>
{/if}

[!LEGACY]
在 Svelte 4 中,您将使用 export let dataexport 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
<form method="POST" action="?/login">
+++	{#if form?.missing}<p class="error">邮箱字段为必填项</p>{/if}
  {#if form?.incorrect}<p class="error">凭据无效!</p>{/if}+++
  <label>
    Email
    <input name="email" type="email" +++value={form?.email ?? ''}+++>
  </label>
  <label>
    Password
    <input name="password" type="password">
  </label>
  <button>Log in</button>
  <button formaction="?/register">Register</button>
</form>

返回的数据必须可序列化为 JSON。除此之外,结构完全由您决定。例如,如果页面上有多个表单,您可以使用 id 属性或类似的方式区分返回的 form 数据对应哪个 <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
<script>
  +++import { enhance } from '$app/forms';+++

  /** @type {{ form: import('./$types').ActionData }} */
  let { form } = $props();
</script>

<form method="POST" +++use:enhance+++>

[!NOTE] use:enhance 只能与 method="POST" 的表单一起使用。它将无法与 method="GET" 一起工作,后者是未指定方法的表单的默认方法。在未指定 method="POST" 的表单上尝试使用 use:enhance 将导致错误。

[!NOTE] 是的,enhance action 和 <form action> 都叫做 ‘action’,这些文档充满了各种 action。抱歉。

没有参数时,use:enhance 将模拟浏览器原生行为,只是不进行完整页面重载。它将:

  • 在成功或无效响应时更新 form 属性、page.formpage.status,但仅当 action 在您提交的同一页面上时。例如,如果您的表单看起来像 <form action="/somewhere/else" ..>form 属性和 page.form 状态将 不会 更新。这是因为在本地表单提交的情况下,您将被重定向到 action 所在的页面。如果您希望无论如何都能更新,使用 applyAction
  • 重置 <form> 元素
  • 在成功响应时使用 invalidateAll 使所有数据失效
  • 在重定向响应时调用 goto
  • 如果发生错误,渲染最近的 +error 边界
  • 将焦点重置到适当的元素

自定义 use:enhance

要自定义行为,您可以提供一个 SubmitFunction,它会在表单提交前立即运行,并(可选地)返回一个随 ActionResult 一起运行的回调。请注意,如果您返回一个回调,上述默认行为将不会被触发。要恢复默认行为,请调用 update

<form
  method="POST"
  use:enhance={({ formElement, formData, action, cancel, submitter }) => {
    // `formElement` 是这个 `<form>` 元素
    // `formData` 是即将提交的 `FormData` 对象
    // `action` 是表单提交的 URL
    // 调用 `cancel()` 将阻止提交
    // `submitter` 是导致表单提交的 `HTMLElement`

    return async ({ result, update }) => {
      // `result` 是一个 `ActionResult` 对象
      // `update` 是一个触发默认逻辑的函数,如果没有设置此回调
    };
  }}
>

您可以使用这些函数来显示和隐藏加载界面等。

如果您返回一个回调,您可能需要重现部分默认的 use:enhance 行为,但在成功响应时不使所有数据失效。您可以使用 applyAction 来实现:

/// file: src/routes/login/+page.svelte
<script>
  import { enhance, +++applyAction+++ } from '$app/forms';

  /** @type {{ form: import('./$types').ActionData }} */
  let { form } = $props();
</script>

<form
  method="POST"
  use:enhance={({ formElement, formData, action, cancel }) => {
    return async ({ result }) => {
      // `result` 是一个 `ActionResult` 对象
+++			if (result.type === 'redirect') {
        goto(result.location);
      } else {
        await applyAction(result);
      }+++
    };
  }}
>

applyAction(result) 的行为取决于 result.type

  • success, failure — 将 page.status 设置为 result.status,并将 formpage.form 更新为 result.data(无论您从哪里提交,这与 enhanceupdate 形成对比)
  • redirect — 调用 goto(result.location, { invalidateAll: true })
  • error — 使用 result.error 渲染最近的 +error 边界

在所有情况下,焦点将被重置。

自定义事件监听器

我们也可以不使用 use:enhance,在 <form> 上使用普通的事件监听器,自己实现渐进式增强:

<!--- file: src/routes/login/+page.svelte --->
<script>
  import { invalidateAll, goto } from '$app/navigation';
  import { applyAction, deserialize } from '$app/forms';

  /** @type {{ form: import('./$types').ActionData }} */
  let { form } = $props();

  /** @param {SubmitEvent & { currentTarget: EventTarget & HTMLFormElement}} event */
  async function handleSubmit(event) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);

    const response = await fetch(event.currentTarget.action, {
      method: 'POST',
      body: data
    });

    /** @type {import('@sveltejs/kit').ActionResult} */
    const result = deserialize(await response.text());

    if (result.type === 'success') {
      // 重新运行所有 `load` 函数,跟随成功的更新
      await invalidateAll();
    }

    applyAction(result);
  }
</script>

<form method="POST" onsubmit={handleSubmit}>
  <!-- content -->
</form>

请注意,在使用 $app/forms 中相应的方法进一步处理响应之前,需要 deserialize 响应。仅 JSON.parse() 是不够的,因为表单 action(如 load 函数)也支持返回 DateBigInt 对象。

如果您在 +page.server.js 旁边有一个 +server.jsfetch 请求将默认路由到那里。要改为 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。以下是这种交互的示例:

<!--- file: src/routes/send-message/+page.svelte --->
<script>
  function rerun() {
    fetch('/api/ci', {
      method: 'POST'
    });
  }
</script>

<button onclick={rerun}>Rerun CI</button>
// @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 将像处理 <a> 元素一样处理它们,使用客户端路由而不是完整页面导航:

<form action="/search">
	<label>
		Search
		<input name="q" />
	</label>
</form>

提交此表单将导航到 /search?q=... 并调用您的 load 函数,但不会调用 action 。与 <a> 元素一样,您可以在 <form> 上设置 data-sveltekit-reloaddata-sveltekit-replacestatedata-sveltekit-keepfocus 以及 data-sveltekit-noscroll 属性,以控制路由器的行为。

进一步阅读

  • 教程:表单

Svelte 中文文档

点击查看中文文档 - SvelteKit 表单 actions。

系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

此外我还写过 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答读者问等 14 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2317094.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣hot100二刷——二叉树

第二次刷题不在idea写代码&#xff0c;而是直接在leetcode网站上写&#xff0c;“逼”自己掌握常用的函数。 标志掌握程度解释办法⭐Fully 完全掌握看到题目就有思路&#xff0c;编程也很流利⭐⭐Basically 基本掌握需要稍作思考&#xff0c;或者看到提示方法后能解答⭐⭐⭐Sl…

字符串哈希从入门到精通

一、基本概念 字符串哈希是将任意长度的字符串映射为固定长度的哈希值&#xff08;通常为整数&#xff09;的技术&#xff0c;核心目标是实现O(1)时间的子串快速比较和高效查询。其本质是通过数学运算将字符串转换为唯一性较高的数值&#xff0c;例如&#xff1a; ​​​​​​…

C语言:编程设计猜数游戏

先由计算机想一个数给用户猜&#xff0c;如果猜对了&#xff0c;提示“right&#xff01;”&#xff0c;猜错了&#xff0c;提示“wrong&#xff01;及大小” 思路&#xff1a;用随机函数rand&#xff08;&#xff09;取到计算机想的数 代码&#xff1a; #include <stdio.…

win10 c++ VsCode 配置PCL open3d并显示

win10 c VsCode配置PCL open3d并显示 一、效果图二、配置步骤2.1 安装vscode2.2 pcl-open3d配置2.3 vscode中设置 三、测试代码四、注意事项及后续 一、效果图 二、配置步骤 2.1 安装vscode vscode下载链接 下载中文插件、c相关插件 2.2 pcl-open3d配置 1&#xff09;下载…

Vala 开发环境搭建

介绍 Vala 是一种使用现代高级抽象的编程语言&#xff0c;与用 C 语言编写的应用程序和库相比&#xff0c;没有施加额外的运行时要求&#xff0c;也不需要使用不同的 ABI。 Vala 使用 GObject 类型系统&#xff0c;并具有额外的代码生成例程&#xff0c;使面向 GNOME 堆栈变得简…

【网页】自制流光卡片

概述 小红书有个博主自己搞的笔记排版工具叫“流光卡片”&#xff0c;类似的还有个Markdown排版工具叫MD2Card。 我这个版本类似&#xff0c;但是自己写的东西&#xff0c;控制性更好。 初期就写了个静态页面&#xff0c;后期结合Godot快速生成&#xff0c;并可能结合JS库&a…

CSP-J/S冲奖第18天:真题解析

解题步骤 读取输入&#xff1a;首先读取整数n&#xff0c;然后读取n个正整数并存储在一个数组或容器中。 排序数组&#xff1a;对数组进行排序&#xff0c;以便后续使用双指针法高效查找。 遍历数组&#xff1a;对于每个数target&#xff0c;检查是否存在另外两个不同的数a和…

【linux】虚拟机执行sudo yum isntall perl报错 could not retrieve mirrorlist htt:

项目场景&#xff1a; 提示&#xff1a;虚拟机安装拓展包&#xff0c;sudo yum install perl Virtualbox 在不安装增强功能扩展的情况下, 无法自适应分辨率和共享剪切板等操作 问题描述 原因分析&#xff1a; 提示&#xff1a;这里填写问题的分析&#xff1a; 出现这个错误是因…

旅游类小程序界面设计

产品概述 艾啦游是一款互联网旅游类小程序&#xff0c;致力于国内精品旅游&#xff0c;以及拥有自由行、专属热榜单、出行攻略等诸多功能&#xff0c;汇聚了许多国内的人气景点&#xff0c;与诸多城市的酒店也保持合作&#xff0c;打造一体式旅行服务&#xff0c;更有不断上新…

DQN 玩 2048 实战|第三期!优化网络,使用GPU、Env奖励优化

视频讲解&#xff1a; DQN 玩 2048 实战&#xff5c;第三期&#xff01;优化网络&#xff0c;使用GPU、Env奖励优化 1. 仅考虑局部合并奖励&#xff1a;目前的奖励只设置为合并方块时获得的分数&#xff0c;只关注了每一步的即时合并收益&#xff0c;而没有对最终达成 2048 这个…

【python】http post 在body中传递json数据 以发送

http post 在body中传递json数据 以发送&#xff0c;json的格式非常重要这里要传递json对象&#xff0c;而不是一个json字符串 传递post一个 JSON 字符串 是ok的 是的&#xff0c; {"rsource_rhythm_action_list": {"name": "AI_\\u6708\\u4eae\\u…

[贪心算法]-最大数(lambda 表达式的补充)

1.解析 我们一般使用的排序比较大小都是 a>b 那么a在b的前面 ab 无所谓 a<b a在b的后面 本题的排序则是 ab>ba 那么a在b的前面 abba 无所谓 ab<ba a在b的后面 2.代码 class Solution { public:string largestNumber(vector<int>& nums) {//1.先把所有…

C语言 —— 此去经年梦浪荡魂音 - 深入理解指针(卷二)

目录 1. 数组名与地址 2. 指针访问数组 3.一维数组传参本质 4.二级指针 5. 指针数组 6. 指针数组模拟二维数组 1. 数组名与地址 我们先看下面这个代码&#xff1a; int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int* p &arr[0]; 这里我们使用 &arr[0] 的方式拿到了数…

python实现简单的图片去水印工具

python实现简单的图片去水印工具 使用说明&#xff1a; 点击"打开图片"选择需要处理的图片 在图片上拖拽鼠标选择水印区域&#xff08;红色矩形框&#xff09; 点击"去除水印"执行处理 点击"保存结果"保存处理后的图片 运行效果 先简要说明…

使用dify+deepseek部署本地知识库

使用difydeepseek部署本地知识库 一、概述二、安装windows docker desktop1、确认系统的Hyper-v功能正常启用2、docker官网下载安装windows客户端3、安装完成后的界面如下所示 三、下载安装ollama四、部署本地deepseek五、本地下载部署dify5.1 下载dify的安装包5.2 将dify解压到…

【算法day13】最长公共前缀

最长公共前缀 https://leetcode.cn/problems/longest-common-prefix/submissions/612055945/ 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 class Solution { public:string longestCommonPrefix(vector<string&g…

Java高频面试之集合-13

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试官&#xff1a;为什么 hash 函数能降哈希碰撞&#xff1f; 哈希函数通过以下核心机制有效降低碰撞概率&#xff0c;确保不同输入尽可能映…

RGV调度算法(三)--遗传算法

1、基于时间窗 https://wenku.baidu.com/view/470e9fd8b4360b4c2e3f5727a5e9856a57122693.html?_wkts_1741880736197&bdQuery%E7%8E%AF%E7%A9%BF%E8%B0%83%E5%BA%A6%E7%AE%97%E6%B3%95 2.2019年MathorCup高校数学建模挑战赛B题 2019-mathorcupB题-环形穿梭机调度模型&a…

YOLOv8轻量化改进——Coordinate Attention注意力机制

现在针对YOLOv8的架构改进越来越多&#xff0c;今天尝试引入了Coordinate Attention注意力机制以改进对小目标物体的检测效率。 yolov8的下载和安装参考我这篇博客&#xff1a; 基于SeaShips数据集的yolov8训练教程_seaships处理成yolov8-CSDN博客 首先我们可以去官网找到CA注…

基于SpringBoot+Vue的驾校预约管理系统+LW示例参考

1.项目介绍 系统角色&#xff1a;管理员、普通用户、教练功能模块&#xff1a;用户管理、管理员管理、教练管理、教练预约管理、车辆管理、车辆预约管理、论坛管理、基础数据管理等技术选型&#xff1a;SpringBoot&#xff0c;Vue等测试环境&#xff1a;idea2024&#xff0c;j…