一个 `+page.server.js` 文件可以导出 *actions*,这些操作允许您使用 `POST` 数据通过 `
``` 如果有人点击按钮,浏览器将通过`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 ``` > \[注意\] 我们不能在命名操作旁边有默认操作,因为如果您在没有重定向的情况下向命名操作 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 ``` 返回的数据必须可序列化为 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 ``` 请注意,在进一步使用相应方法从 `$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=...` 并调用您的加载函数,但不会调用操作。与 `` 元素一样,您可以在 `