一个 `+page.server.js` 文件可以导出 *actions*,这些操作允许您使用 `POST` 数据通过 `
` 元素发送到服务器。 当使用``时,客户端 JavaScript 是可选的,但您可以使用 JavaScript 轻松地*渐进式增强*您的表单交互,以提供最佳的用户体验。 ## 默认操作 在最简单的情况下,一个页面声明了一个 `默认`动作: ```js /// file: src/routes/login/+page.server.js /** @satisfies {import('./$types').Actions} */ export const actions = { default: async (event) => { // TODO log the user in } }; ``` 要从此 `/login` 页面调用此操作,只需添加一个 `` — 不需要 JavaScript: ```svelte
``` 如果有人点击按钮,浏览器将通过`POST`请求将表单数据发送到运行默认操作的服务器。 > \[!注意\] 操作始终使用 `POST` 请求,因为 `GET` 请求不应产生副作用。 我们也可以从其他页面调用操作(例如,如果根布局中的导航中有登录小部件)通过添加`操作`属性,指向页面: ```html /// file: src/routes/+layout.svelte
``` ## 命名动作 而不是一个 `默认` 动作,一个页面可以有它需要的任意多个命名动作: ```js /// 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 }+++ }; ``` 调用命名操作时,添加一个以`/`字符为前缀的查询参数: ```svelte
``` ```svelte ``` 除了 `action` 属性外,我们还可以在按钮上使用 `formaction` 属性将相同的表单数据 `POST` 到与父 `` 不同的操作: ```svelte /// file: src/routes/login/+page.svelte ++++++
``` > \[注意\] 我们不能在命名操作旁边有默认操作,因为如果您在没有重定向的情况下向命名操作 POST,查询参数将保留在 URL 中,这意味着下一个默认 POST 将通过之前的命名操作进行。 ## 动作解剖 每个操作都接收一个 `RequestEvent` 对象,允许您使用 `request.formData()` 读取数据。在处理请求(例如,通过设置 cookie 登录用户)之后,操作可以响应将通过相应页面的 `form` 属性和通过 `page.form` 在整个应用范围内直到下一次更新可用的数据。 ```js /// 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 } }; ``` ```svelte {#if form?.success}

Successfully logged in! Welcome back, {data.user.name}

{/if} ``` > \[!旧版\] `PageProps` 在 2.16.0 版本中添加。在早期版本中,您必须单独输入 `data` 和 `form` 属性: > > ```js > /// file: +page.svelte > /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ > let { data, form } = $props(); > ``` > > 在 Svelte 4 中,您会使用`export let data`和`export let form`来声明属性。 ### 验证错误 如果请求因数据无效而无法处理,您可以返回验证错误——连同之前提交的表单值——给用户,以便他们可以再次尝试。\``fail`\`函数允许您返回一个 HTTP 状态码(通常是 400 或 422,在验证错误的情况下)以及数据。状态码通过\``page.status`\`获取,数据通过\``form`\`获取: ```js /// 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 } }; ``` > \[!注意\] 注意,作为预防措施,我们只将电子邮件返回到页面,而不是密码。 ```svelte /// file: src/routes/login/+page.svelte
+++ {#if form?.missing}

The email field is required

{/if} {#if form?.incorrect}

Invalid credentials!

{/if}+++
``` 返回的数据必须可序列化为 JSON。除此之外,结构完全由您决定。例如,如果您在页面上有多个表单,您可以使用一个`form`的`form`数据,通过一个`id`属性或类似的方式来区分它们。 ### 重定向 重定向(和错误)与在[`加载`](load#Redirects)中工作完全相同: ```js // @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 } }; ``` ## 加载数据 在动作执行后,页面将重新渲染(除非发生重定向或意外错误),动作的返回值将以 `form` 属性的形式提供给页面。这意味着您的页面 `load` 函数将在动作完成后运行。 请注意,`handle` 在调用动作之前运行,并在 `load` 函数之前不会重新运行。这意味着,例如,如果您使用 `handle` 根据 cookie 填充 `event.locals`,则在动作中设置或删除 cookie 时,您必须更新 `event.locals`。 ```js /// 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); } ``` ```js /// 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; } }; ``` ## 渐进增强 在前面的章节中,我们构建了一个 `/login` 动作,该动作 [无需客户端 JavaScript](https://kryogenix.org/code/browser/everyonehasjs.html) 即可运行——没有看到任何 `fetch`。这很好,但当 JavaScript *可用* 时,我们可以逐步增强我们的表单交互,以提供更好的用户体验。 ### 使用:增强 最简单的方法逐步增强表单是添加 `use:enhance` 操作: ```svelte /// file: src/routes/login/+page.svelte
``` > \[注意\] `use:enhance` 只能与具有 `method="POST"` 的表单以及指向定义在 `+page.server.js` 文件中的操作的表单一起使用。它不能与 `method="GET"` 一起使用,这是未指定方法的表单的默认方法。在未指定 `method="POST"` 的表单上使用 `use:enhance` 或向 `+server.js` 端点提交将导致错误。 > \[!注意\] 是的,有点令人困惑,`增强`操作和``都被称为'操作'。这些文档充满了操作。抱歉。 无参数时,使用`use:enhance`将模拟浏览器原生行为,只是没有整个页面的刷新。它将: * 更新 `表单` 属性、`page.form` 和 `page.status` 在成功或无效响应时,但仅当操作是在您提交的同一页面上时。例如,如果您的表单看起来像 `` ,则 `表单` 属性和 `page.form` 状态将 *不会* 更新。这是因为在本机表单提交的情况下,您将被重定向到操作所在的页面。如果您想无论哪种情况都更新它们,请使用 [`applyAction`](#Progressive-enhancement-Customising-use:enhance)。 * 重置 `` 元素 * 使用`invalidateAll`在成功响应中使所有数据无效 * 调用重定向响应中的 `goto` * 渲染最近的 `+错误` 边界,如果发生错误 * [重置焦点](accessibility#Focus-management)到适当的元素 ### 自定义使用:增强 要自定义行为,您可以在表单提交前提供一个立即运行的 `SubmitFunction`,并且(可选地)返回一个在 `ActionResult` 中运行的回调。 ```svelte { // `formElement` is this `` element // `formData` is its `FormData` object that's about to be submitted // `action` is the URL to which the form is posted // calling `cancel()` will prevent the submission // `submitter` is the `HTMLElement` that caused the form to be submitted return async ({ result, update }) => { // `result` is an `ActionResult` object // `update` is a function which triggers the default logic that would be triggered if this callback wasn't set }; }} > ``` 您可以使用这些函数来显示和隐藏加载 UI 等。 如果您返回一个回调,您将覆盖默认的提交后行为。要恢复,请调用`update`,它接受`invalidateAll`和`reset`参数,或者在对结果使用`applyAction`: ```svelte /// file: src/routes/login/+page.svelte { return async ({ result }) => { // `result` is an `ActionResult` object +++ if (result.type === 'redirect') { goto(result.location); } else { await applyAction(result); }+++ }; }} > ``` 行为取决于 `applyAction(result)` 中的 `result.type`: * `成功`,`失败` — 将`page.status`设置为`result.status`,并更新`form`和`page.form`为`result.data`(无论您从哪里提交,与从`enhance`的`update`相比) * `重定向` — 调用 `goto(result.location, { invalidateAll: true })` * `错误` — 以 `+错误` 边界渲染 `result.error` 在所有情况下,[焦点将被重置](accessibility#Focus-management)。 ### 自定义事件监听器 我们也可以自行实现渐进增强,无需使用`use:enhance`,只需在``上添加一个正常的事件监听器: ```svelte
``` 请注意,在进一步使用相应方法从 `$app/forms` 处理之前,您需要先 `反序列化` 响应。仅使用 `JSON.parse()` 不够,因为表单操作 - 如 `load` 函数 - 也支持返回 `Date` 或 `BigInt` 对象。 如果您旁边有一个`+server.js`和`+page.server.js`,默认情况下,`fetch`请求将被路由到那里。要向`+page.server.js`中的操作`POST`,请使用自定义`x-sveltekit-action`头: ```js const response = await fetch(this.action, { method: 'POST', body: data, +++ headers: { 'x-sveltekit-action': 'true' }+++ }); ``` ## 替代方案 表单操作是向服务器发送数据的首选方式,因为它们可以被逐步增强,但您也可以使用 [`+server.js`](routing#server) 文件来公开(例如)一个 JSON API。以下是这样一种交互可能看起来像: ```svelte ``` ```js // @errors: 2355 1360 2322 /// file: src/routes/api/ci/+server.js /** @type {import('./$types').RequestHandler} */ export function POST() { // do something } ``` ## GET 与 POST 我们已看到,要调用表单操作,您必须使用`method="POST"`。 一些表单不需要向服务器发送`POST`数据——例如搜索输入。对于这些,您可以使用`method="GET"`(或者,等价地,根本不设置`method`),SvelteKit 会将它们视为``元素,使用客户端路由而不是完整页面导航: ```html
``` 提交此表单将导航到 `/search?q=...` 并调用您的加载函数,但不会调用操作。与 `
` 元素一样,您可以在 `
` 上设置 [`data-sveltekit-reload`](link-options#data-sveltekit-reload)、[`data-sveltekit-replacestate`](link-options#data-sveltekit-replacestate)、[`data-sveltekit-keepfocus`](link-options#data-sveltekit-keepfocus) 和 [`data-sveltekit-noscroll`](link-options#data-sveltekit-noscroll) 属性来控制路由器的行为。 ## 进一步阅读 * [ 教程:表单](/tutorial/kit/the-form-element)