核心概念
表单操作
在 GitHub 上编辑此页面+page.server.js 文件可以导出操作,使用 <form> 元素允许你将数据 POST 到服务器。
使用 <form> 时,客户端 JavaScript 是可选的,但你可以轻松地使用 JavaScript 逐步增强你的表单交互,以提供最佳用户体验。
默认操作永久链接
在最简单的情况下,页面声明一个 default 操作
ts/** @type {import('./$types').Actions} */export constactions = {default : async (event ) => {// TODO log the user in}};
tsimport type {Actions } from './$types';export constactions = {default : async (event ) => {// TODO log the user in},} satisfiesActions ;
要从 /login 页面调用此操作,只需添加一个 <form> — 无需 JavaScript
<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 请求将表单数据发送到服务器,运行默认操作。
操作始终使用
POST请求,因为GET请求不应产生副作用。
我们还可以通过添加 action 属性(指向页面)从其他页面调用操作(例如,如果根布局导航中有一个登录小部件)
<form method="POST" action="/login">
<!-- content -->
</form>命名操作永久链接
页面可以拥有任意数量的命名操作,而不是一个 default 操作
/** @type {import('./$types').Actions} */
export const actions = {
default: async (event) => {
login: async (event) => {
// TODO log the user in
},
register: async (event) => {
// TODO register the user
}
};
要调用命名操作,请添加一个查询参数,其名称前缀为 / 字符
<form method="POST" action="?/register"><form method="POST" action="/login?/register">除了 action 属性,我们还可以在按钮上使用 formaction 属性,将相同表单数据 POST 到与父 <form> 不同的操作
<form method="POST">
<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>
我们不能将默认操作放在命名操作旁边,因为如果你在没有重定向的情况下
POST到命名操作,查询参数将保留在 URL 中,这意味着下一个默认POST将通过之前的命名操作进行。
操作的解剖永久链接
每个操作都会接收一个 RequestEvent 对象,允许你使用 request.formData() 读取数据。处理完请求后(例如,通过设置 cookie 来登录用户),操作可以使用数据进行响应,这些数据可以通过相应页面上的 form 属性和整个应用中的 $page.form 获得,直到下一次更新。
ts/** @type {import('./$types').PageServerLoad} */export async functionCannot find name 'db'.2304Cannot find name 'db'.load ({cookies }) {constCannot find name 'db'.2304Cannot find name 'db'.user = awaitdb .getUserFromSession ( cookies .get ('sessionid'));return {user };}/** @type {import('./$types').Actions} */export constactions = {login : async ({cookies ,request }) => {constdata = awaitrequest .formData ();constdata .get ('email');constpassword =data .get ('password');constuser = awaitdb .getUser (cookies .set ('sessionid', awaitdb .createSession (user ), {path : '/' });return {success : true };},register : async (event ) => {// TODO register the user}};
tsimport type {PageServerLoad ,Actions } from './$types';Cannot find name 'db'.2304Cannot find name 'db'.export constCannot find name 'db'.2304Cannot find name 'db'.load :PageServerLoad = async ({cookies }) => {constuser = awaitdb .getUserFromSession (cookies .get ('sessionid'));return {user };};export constactions = {login : async ({cookies ,request }) => {constdata = awaitrequest .formData ();constdata .get ('email');constpassword =data .get ('password');constuser = awaitdb .getUser (cookies .set ('sessionid', awaitdb .createSession (user ), {path : '/' });return {success : true };},register : async (event ) => {// TODO register the user},} satisfiesActions ;
<script>
/** @type {import('./$types').PageData} */
export let data;
/** @type {import('./$types').ActionData} */
export let form;
</script>
{#if form?.success}
<!-- this message is ephemeral; it exists because the page was rendered in
response to a form submission. it will vanish if the user reloads -->
<p>Successfully logged in! Welcome back, {data.user.name}</p>
{/if}<script lang="ts">
import type { PageData, ActionData } from './$types';
export let data: PageData;
export let form: ActionData;
</script>
{#if form?.success}
<!-- this message is ephemeral; it exists because the page was rendered in
response to a form submission. it will vanish if the user reloads -->
<p>Successfully logged in! Welcome back, {data.user.name}</p>
{/if}验证错误永久链接
如果由于数据无效而无法处理请求,你可以将验证错误(以及之前提交的表单值)返回给用户,以便他们可以重试。fail 函数允许你返回一个 HTTP 状态代码(在验证错误的情况下通常为 400 或 422),以及数据。状态代码可以通过 $page.status 获得,数据可以通过 form 获得。
import { fail } from '@sveltejs/kit';
/** @type {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 !== 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
}
};
请注意,作为预防措施,我们只将电子邮件返回给页面,而不是密码。
<form method="POST" action="?/login">
{#if form?.missing}<p class="error">The email field is required</p>{/if}
{#if form?.incorrect}<p class="error">Invalid credentials!</p>{/if}
<label>
Email
<input name="email" type="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 中完全相同。
import { fail, redirect } from '@sveltejs/kit';
/** @type {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 !== 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。
ts/** @type {import('@sveltejs/kit').Handle} */export async functionhandle ({event ,resolve }) {event .locals .user = awaitgetUser (event .cookies .get ('sessionid'));returnresolve (event );}
tsimport type {Handle } from '@sveltejs/kit';export consthandle :Handle = async ({event ,resolve }) => {event .locals .user = awaitgetUser (event .cookies .get ('sessionid'));returnresolve (event );};
ts/** @type {import('./$types').PageServerLoad} */export functionload (event ) {return {user :event .locals .user };}/** @type {import('./$types').Actions} */export constactions = {logout : async (event ) => {event .cookies .delete ('sessionid', {path : '/' });event .locals .user = null;}};
tsimport type {PageServerLoad ,Actions } from './$types';export constload :PageServerLoad = (event ) => {return {user :event .locals .user ,};};export constactions = {logout : async (event ) => {event .cookies .delete ('sessionid', {path : '/' });event .locals .user = null;},} satisfiesActions ;
渐进增强永久链接
在前面的部分中,我们构建了一个 /login 操作,它无需客户端 JavaScript 即可工作——看不到 fetch。这很好,但是当 JavaScript 可用时,我们可以逐步增强表单交互,以提供更好的用户体验。
use:enhance永久链接
逐步增强表单的最简单方法是添加 use:enhance 操作
<script>
import { enhance } from '$app/forms';
/** @type {import('./$types').ActionData} */
export let form;
</script>
<form method="POST" use:enhance>
是的,有点令人困惑,因为
enhance操作和<form action>都称为“操作”。这些文档充满了动作。抱歉。
如果没有参数,use:enhance 将模拟浏览器的原生行为,只是没有整页重新加载。它将
- 在成功或无效的响应中更新
form属性、$page.form和$page.status,但前提是操作与您提交的页面相同。例如,如果您的表单看起来像<form action="/somewhere/else" ..>,则 不会 更新form和$page。这是因为在原生表单提交情况下,您将被重定向到操作所在的页面。如果您希望无论如何更新它们,请使用applyAction - 重置
<form>元素 - 在成功响应时使用
invalidateAll使所有数据无效 - 在重定向响应时调用
goto - 如果发生错误,则渲染最接近的
+error边界 - 重置焦点到适当的元素
自定义 use:enhance永久链接
要自定义行为,您可以提供一个 SubmitFunction,它在提交表单之前立即运行,并且(可选)返回一个使用 ActionResult 运行的回调。请注意,如果您返回回调,则不会触发上面提到的默认行为。要恢复它,请调用 update。
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel, submitter }) => {
// `formElement` is this `<form>` 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 等。
如果您返回回调,您可能需要复制部分默认 use:enhance 行为,但不要在成功响应时使所有数据无效。您可以使用 applyAction 来做到这一点
<script>
import { enhance, applyAction } from '$app/forms';
/** @type {import('./$types').ActionData} */
export let form;
</script>
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel }) => {
return async ({ result }) => {
// `result` is an `ActionResult` object
if (result.type === 'redirect') {
goto(result.location);
} else {
await applyAction(result);
}
};
}}
>
applyAction(result) 的行为取决于 result.type
success、failure——将$page.status设置为result.status,并将form和$page.form更新为result.data(无论您从哪里提交,这与enhance中的update相反)redirect— 调用goto(result.location, { invalidateAll: true })error— 使用result.error渲染最近的+error边界
在所有情况下,焦点都将重置。
自定义事件侦听器永久链接
我们还可以自己实现渐进增强,无需 use:enhance,只需在 <form> 上使用一个普通的事件侦听器
<script>
import { invalidateAll, goto } from '$app/navigation';
import { applyAction, deserialize } from '$app/forms';
/** @type {import('./$types').ActionData} */
export let form;
/** @type {any} */
let error;
/** @param {{ currentTarget: EventTarget & HTMLFormElement}} event */
async function handleSubmit(event) {
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') {
// rerun all `load` functions, following the successful update
await invalidateAll();
}
applyAction(result);
}
</script>
<form method="POST" on:submit|preventDefault={handleSubmit}>
<!-- content -->
</form><script lang="ts">
import { invalidateAll, goto } from '$app/navigation';
import { applyAction, deserialize } from '$app/forms';
import type { ActionData } from './$types';
import type { ActionResult } from '@sveltejs/kit';
export let form: ActionData;
let error: any;
async function handleSubmit(event: { currentTarget: EventTarget & HTMLFormElement }) {
const data = new FormData(event.currentTarget);
const response = await fetch(event.currentTarget.action, {
method: 'POST',
body: data,
});
const result: ActionResult = deserialize(await response.text());
if (result.type === 'success') {
// rerun all `load` functions, following the successful update
await invalidateAll();
}
applyAction(result);
}
</script>
<form method="POST" on:submit|preventDefault={handleSubmit}>
<!-- content -->
</form>请注意,在使用 $app/forms 中的相应方法进一步处理响应之前,需要对其进行 deserialize。JSON.parse() 不够,因为表单操作(如 load 函数)还支持返回 Date 或 BigInt 对象。
如果您在 +page.server.js 旁边有一个 +server.js,则 fetch 请求将默认路由到那里。要改为向 +page.server.js 中的操作 POST,请使用自定义 x-sveltekit-action 标头
const response = await fetch(this.action, {
method: 'POST',
body: data,
headers: {
'x-sveltekit-action': 'true'
}
});
替代方案永久链接
表单操作是向服务器发送数据首选方式,因为它们可以逐步增强,但您还可以使用 +server.js 文件来公开(例如)JSON API。以下是这种交互的外观
<script>
function rerun() {
fetch('/api/ci', {
method: 'POST'
});
}
</script>
<button on:click={rerun}>Rerun CI</button><script lang="ts">
function rerun() {
fetch('/api/ci', {
method: 'POST',
});
}
</script>
<button on:click={rerun}>Rerun CI</button>ts/** @type {import('./$types').RequestHandler} */export functionPOST () {// do something}
tsimport type {RequestHandler } from './$types';export constPOST :RequestHandler = () => {// do something};
GET 与 POST永久链接
正如我们所见,要调用表单操作,您必须使用 method="POST"。
有些表单不需要向服务器 POST 数据,例如搜索输入。对于这些,您可以使用 method="GET"(或等效地,根本不使用 method),SvelteKit 将把它们当作 <a> 元素,使用客户端路由器而不是完整的页面导航
<form action="/search">
<label>
Search
<input name="q">
</label>
</form>提交此表单将导航到 /search?q=... 并调用您的加载函数,但不会调用操作。与 <a> 元素一样,您可以设置 <form> 上的 data-sveltekit-reload、data-sveltekit-replacestate、data-sveltekit-keepfocus 和 data-sveltekit-noscroll 属性来控制路由器的行为。