Skip to main content

表单操作

+page.server.js 文件可以导出操作,这允许你使用 <form> 元素将数据 POST 传输到服务器。

¥A +page.server.js file can export actions, which allow you to POST data to the server using the <form> element.

使用 <form> 时,客户端 JavaScript 是可选的,但你可以轻松地逐步增强与 JavaScript 的表单交互,以提供最佳用户体验。

¥When using <form>, client-side JavaScript is optional, but you can easily progressively enhance your form interactions with JavaScript to provide the best user experience.

默认操作(Default actions)

¥Default actions

在最简单的情况下,页面声明一个 default 操作:

¥In the simplest case, a page declares a default action:

src/routes/login/+page.server
/** @satisfies {import('./$types').Actions} */
export const 
const actions: {
    default: (event: any) => Promise<void>;
}
@satisfies{import('./$types').Actions}
actions
= {
default: (event: any) => Promise<void>default: async (event: anyevent) => { // TODO log the user in } };
import type { 
type Actions = {
    [x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
    [x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions
} from './$types';
export const
const actions: {
    default: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>;
}
actions
= {
default: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>default: async (event: Kit.RequestEvent<Record<string, any>, string | null>event) => { // TODO log the user in } } satisfies
type Actions = {
    [x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
    [x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions
;

要从 /login 页面调用此操作,只需添加 <form> — 无需 JavaScript:

¥To invoke this action from the /login page, just add a <form> — no JavaScript needed:

src/routes/login/+page
<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 请求将表单数据发送到服务器,运行默认操作。

¥If someone were to click the button, the browser would send the form data via POST request to the server, running the default action.

操作始终使用 POST 请求,因为 GET 请求永远不会有副作用。

¥[!NOTE] Actions always use POST requests, since GET requests should never have side-effects.

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

¥We can also invoke the action from other pages (for example if there’s a login widget in the nav in the root layout) by adding the action attribute, pointing to the page:

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

命名操作(Named actions)

¥Named actions

页面可以拥有任意数量的命名操作,而不是一个 default 操作:

¥Instead of one default action, a page can have as many named actions as it needs:

src/routes/login/+page.server
/** @satisfies {import('./$types').Actions} */
export const 
const actions: {
    login: (event: any) => Promise<void>;
    register: (event: any) => Promise<void>;
}
@satisfies{import('./$types').Actions}
actions
= {
default: async (event) => { login: (event: any) => Promise<void>login: async (event: anyevent) => { // TODO log the user in }, register: (event: any) => Promise<void>register: async (event: anyevent) => { // TODO register the user } };
import type { 
type Actions = {
    [x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
    [x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions
} from './$types';
export const
const actions: {
    login: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>;
    register: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<...>;
}
actions
= {
default: async (event) => { login: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>login: async (event: Kit.RequestEvent<Record<string, any>, string | null>event) => { // TODO log the user in }, register: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: Kit.RequestEvent<Record<string, any>, string | null>event) => { // TODO register the user } } satisfies
type Actions = {
    [x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
    [x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions
;

要调用命名操作,请添加一个查询参数,其名称以 / 字符为前缀:

¥To invoke a named action, add a query parameter with the name prefixed by a / character:

src/routes/login/+page
<form method="POST" action="?/register">
src/routes/+layout
<form method="POST" action="/login?/register">

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

¥As well as the action attribute, we can use the formaction attribute on a button to POST the same form data to a different action than the parent <form>:

src/routes/login/+page
<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 将通过之前的命名操作。

¥[!NOTE] We can’t have default actions next to named actions, because if you POST to a named action without a redirect, the query parameter is persisted in the URL, which means the next default POST would go through the named action from before.

操作的剖析(Anatomy of an action)

¥Anatomy of an action

每个操作都会收到一个 RequestEvent 对象,允许你使用 request.formData() 读取数据。处理请求后(例如,通过设置 cookie 登录用户),操作可以响应数据,这些数据将通过相应页面上的 form 属性和整个应用范围内的 page.form 提供,直到下一次更新。

¥Each action receives a RequestEvent object, allowing you to read the data with request.formData(). After processing the request (for example, logging the user in by setting a cookie), the action can respond with data that will be available through the form property on the corresponding page and through page.form app-wide until the next update.

src/routes/login/+page.server
import * as module "$lib/server/db"db from '$lib/server/db';

/** @type {import('./$types').PageServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
@type{import('./$types').PageServerLoad}
load
({ cookies: Cookies

Get or set cookies related to the current request

cookies
}) {
const const user: anyuser = await module "$lib/server/db"db.getUserFromSession(cookies: Cookies

Get or set cookies related to the current request

cookies
.Cookies.get(name: string, opts?: CookieParseOptions): string | undefined

Gets a cookie that was previously set with cookies.set, or from the request headers.

@paramname the name of the cookie
@paramopts the options, passed directly to cookie.parse. See documentation here
get
('sessionid'));
return { user: anyuser }; } /** @satisfies {import('./$types').Actions} */ export const
const actions: {
    login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
        success: boolean;
    }>;
    register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<...>;
}
@satisfies{import('./$types').Actions}
actions
= {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
    success: boolean;
}>
login
: async ({ cookies: Cookies

Get or set cookies related to the current request

cookies
, request: Request

The original request object

request
}) => {
const const data: FormDatadata = await request: Request

The original request object

request
.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email'); const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password'); const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValue | nullemail); cookies: Cookies

Get or set cookies related to the current request

cookies
.
Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
    path: string;
}): void

Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.

The httpOnly and secure options are true by default (except on http://localhost, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.

You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children

@paramname the name of the cookie
@paramvalue the cookie value
@paramopts the options, passed directly to cookie.serialize. See documentation here
set
('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: string

Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute } . By default, the path is considered the “default path”.

path
: '/' });
return { success: booleansuccess: true }; }, register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => { // TODO register the user } };
import * as module "$lib/server/db"db from '$lib/server/db';
import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad, 
type Actions = {
    [x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions
} from './$types';
export const const load: PageServerLoadload: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad = async ({ cookies: Cookies

Get or set cookies related to the current request

cookies
}) => {
const const user: anyuser = await module "$lib/server/db"db.getUserFromSession(cookies: Cookies

Get or set cookies related to the current request

cookies
.Cookies.get(name: string, opts?: CookieParseOptions): string | undefined

Gets a cookie that was previously set with cookies.set, or from the request headers.

@paramname the name of the cookie
@paramopts the options, passed directly to cookie.parse. See documentation here
get
('sessionid'));
return { user: anyuser }; }; export const
const actions: {
    login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
        success: boolean;
    }>;
    register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<...>;
}
actions
= {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
    success: boolean;
}>
login
: async ({ cookies: Cookies

Get or set cookies related to the current request

cookies
, request: Request

The original request object

request
}) => {
const const data: FormDatadata = await request: Request

The original request object

request
.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email'); const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password'); const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValue | nullemail); cookies: Cookies

Get or set cookies related to the current request

cookies
.
Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
    path: string;
}): void

Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.

The httpOnly and secure options are true by default (except on http://localhost, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.

You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children

@paramname the name of the cookie
@paramvalue the cookie value
@paramopts the options, passed directly to cookie.serialize. See documentation here
set
('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: string

Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute } . By default, the path is considered the “default path”.

path
: '/' });
return { success: booleansuccess: true }; }, register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => { // TODO register the user } } satisfies
type Actions = {
    [x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions
;
src/routes/login/+page
<script>
	/** @type {import('./$types').PageProps} */
	let { data, form } = $props();
</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 { PageProps } from './$types';

	let { data, form }: PageProps = $props();
</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}
Legacy mode

PageProps 是在 2.16.0 中添加的。在早期版本中,你必须分别输入 dataform 属性:

¥[!LEGACY] PageProps was added in 2.16.0. In earlier versions, you had to type the data and form properties individually:

+page
/** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */
let { let data: anydata, let form: anyform } = function $props(): any

Declares the props that a component accepts. Example:

let { optionalProp = 42, requiredProp, bindableProp = $bindable() }: { optionalProp?: number; requiredProps: string; bindableProp: boolean } = $props();

https://svelte.dev/docs/svelte/$props

$props
();
import type { import PageDataPageData, import ActionDataActionData } from './$types';

let { let data: PageDatadata, let form: ActionDataform }: { data: PageDatadata: import PageDataPageData, form: ActionDataform: import ActionDataActionData } = function $props(): any

Declares the props that a component accepts. Example:

let { optionalProp = 42, requiredProp, bindableProp = $bindable() }: { optionalProp?: number; requiredProps: string; bindableProp: boolean } = $props();

https://svelte.dev/docs/svelte/$props

$props
();

在 Svelte 4 中,你将使用 export let dataexport let form 来声明属性。

¥In Svelte 4, you’d use export let data and export let form instead to declare properties.

验证错误(Validation errors)

¥Validation errors

如果由于数据无效而无法处理请求,你可以将验证错误(连同之前提交的表单值)返回给用户,以便他们重试。fail 函数允许你返回 HTTP 状态代码(在验证错误的情况下通常为 400 或 422)以及数据。状态代码可通过 page.status 获得,数据可通过 form 获得:

¥If the request couldn’t be processed because of invalid data, you can return validation errors — along with the previously submitted form values — back to the user so that they can try again. The fail function lets you return an HTTP status code (typically 400 or 422, in the case of validation errors) along with the data. The status code is available through page.status and the data through form:

src/routes/login/+page.server
import { function fail(status: number): ActionFailure<undefined> (+1 overload)

Create an ActionFailure object.

@paramstatus The HTTP status code. Must be in the range 400-599.
fail
} from '@sveltejs/kit';
import * as module "$lib/server/db"db from '$lib/server/db'; /** @satisfies {import('./$types').Actions} */ export const
const actions: {
    login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
        email: string | null;
        missing: boolean;
    }> | ActionFailure<{
        ...;
    }> | {
        ...;
    }>;
    register: (event: RequestEvent<...>) => Promise<...>;
}
@satisfies{import('./$types').Actions}
actions
= {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
    email: string | null;
    missing: boolean;
}> | ActionFailure<{
    email: FormDataEntryValue;
    incorrect: boolean;
}> | {
    ...;
}>
login
: async ({ cookies: Cookies

Get or set cookies related to the current request

cookies
, request: Request

The original request object

request
}) => {
const const data: FormDatadata = await request: Request

The original request object

request
.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email'); const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password'); if (!const email: FormDataEntryValue | nullemail) { return
fail<{
    email: string | null;
    missing: boolean;
}>(status: number, data: {
    email: string | null;
    missing: boolean;
}): ActionFailure<{
    email: string | null;
    missing: boolean;
}> (+1 overload)

Create an ActionFailure object.

@paramstatus The HTTP status code. Must be in the range 400-599.
@paramdata Data associated with the failure (e.g. validation errors)
fail
(400, { email: string | nullemail, missing: booleanmissing: true });
} const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValueemail); if (!const user: anyuser || const user: anyuser.password !== module "$lib/server/db"db.hash(const password: FormDataEntryValue | nullpassword)) { return
fail<{
    email: FormDataEntryValue;
    incorrect: boolean;
}>(status: number, data: {
    email: FormDataEntryValue;
    incorrect: boolean;
}): ActionFailure<{
    email: FormDataEntryValue;
    incorrect: boolean;
}> (+1 overload)

Create an ActionFailure object.

@paramstatus The HTTP status code. Must be in the range 400-599.
@paramdata Data associated with the failure (e.g. validation errors)
fail
(400, { email: FormDataEntryValueemail, incorrect: booleanincorrect: true });
} cookies: Cookies

Get or set cookies related to the current request

cookies
.
Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
    path: string;
}): void

Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.

The httpOnly and secure options are true by default (except on http://localhost, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.

You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children

@paramname the name of the cookie
@paramvalue the cookie value
@paramopts the options, passed directly to cookie.serialize. See documentation here
set
('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: string

Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute } . By default, the path is considered the “default path”.

path
: '/' });
return { success: booleansuccess: true }; }, register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => { // TODO register the user } };
import { function fail(status: number): ActionFailure<undefined> (+1 overload)

Create an ActionFailure object.

@paramstatus The HTTP status code. Must be in the range 400-599.
fail
} from '@sveltejs/kit';
import * as module "$lib/server/db"db from '$lib/server/db'; import type {
type Actions = {
    [x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions
} from './$types';
export const
const actions: {
    login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
        email: string | null;
        missing: boolean;
    }> | ActionFailure<{
        ...;
    }> | {
        ...;
    }>;
    register: (event: RequestEvent<...>) => Promise<...>;
}
actions
= {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
    email: string | null;
    missing: boolean;
}> | ActionFailure<{
    email: FormDataEntryValue;
    incorrect: boolean;
}> | {
    ...;
}>
login
: async ({ cookies: Cookies

Get or set cookies related to the current request

cookies
, request: Request

The original request object

request
}) => {
const const data: FormDatadata = await request: Request

The original request object

request
.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email'); const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password'); if (!const email: FormDataEntryValue | nullemail) { return
fail<{
    email: string | null;
    missing: boolean;
}>(status: number, data: {
    email: string | null;
    missing: boolean;
}): ActionFailure<{
    email: string | null;
    missing: boolean;
}> (+1 overload)

Create an ActionFailure object.

@paramstatus The HTTP status code. Must be in the range 400-599.
@paramdata Data associated with the failure (e.g. validation errors)
fail
(400, { email: string | nullemail, missing: booleanmissing: true });
} const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValueemail); if (!const user: anyuser || const user: anyuser.password !== module "$lib/server/db"db.hash(const password: FormDataEntryValue | nullpassword)) { return
fail<{
    email: FormDataEntryValue;
    incorrect: boolean;
}>(status: number, data: {
    email: FormDataEntryValue;
    incorrect: boolean;
}): ActionFailure<{
    email: FormDataEntryValue;
    incorrect: boolean;
}> (+1 overload)

Create an ActionFailure object.

@paramstatus The HTTP status code. Must be in the range 400-599.
@paramdata Data associated with the failure (e.g. validation errors)
fail
(400, { email: FormDataEntryValueemail, incorrect: booleanincorrect: true });
} cookies: Cookies

Get or set cookies related to the current request

cookies
.
Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
    path: string;
}): void

Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.

The httpOnly and secure options are true by default (except on http://localhost, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.

You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children

@paramname the name of the cookie
@paramvalue the cookie value
@paramopts the options, passed directly to cookie.serialize. See documentation here
set
('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: string

Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute } . By default, the path is considered the “default path”.

path
: '/' });
return { success: booleansuccess: true }; }, register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => { // TODO register the user } } satisfies
type Actions = {
    [x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions
;

请注意,作为预防措施,我们只将电子邮件返回到页面 — 而不是密码。

¥[!NOTE] Note that as a precaution, we only return the email back to the page — not the password.

src/routes/login/+page
<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" 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>

¥The returned data must be serializable as JSON. Beyond that, the structure is entirely up to you. For example, if you had multiple forms on the page, you could distinguish which <form> the returned form data referred to with an id property or similar.

重定向(Redirects)

¥Redirects

重定向(和错误)的工作方式与 load 中的完全相同:

¥Redirects (and errors) work exactly the same as in load:

src/routes/login/+page.server
import { function fail(status: number): ActionFailure<undefined> (+1 overload)

Create an ActionFailure object.

@paramstatus The HTTP status code. Must be in the range 400-599.
fail
, function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never

Redirect a request. When called during request handling, SvelteKit will return a redirect response. Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.

@paramstatus The HTTP status code. Must be in the range 300-308.
@paramlocation The location to redirect to.
@throwsRedirect This error instructs SvelteKit to redirect to the specified location.
@throwsError If the provided status is invalid.
redirect
} from '@sveltejs/kit';
import * as module "$lib/server/db"db from '$lib/server/db'; /** @satisfies {import('./$types').Actions} */ export const
const actions: {
    login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
        email: FormDataEntryValue | null;
        missing: boolean;
    }> | ActionFailure<...> | {
        ...;
    }>;
    register: (event: RequestEvent<...>) => Promise<...>;
}
@satisfies{import('./$types').Actions}
actions
= {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
    email: FormDataEntryValue | null;
    missing: boolean;
}> | ActionFailure<...> | {
    ...;
}>
login
: async ({ cookies: Cookies

Get or set cookies related to the current request

cookies
, request: Request

The original request object

request
, url: URL

The requested URL.

url
}) => {
const const data: FormDatadata = await request: Request

The original request object

request
.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email'); const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password'); const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValue | nullemail); if (!const user: anyuser) { return
fail<{
    email: FormDataEntryValue | null;
    missing: boolean;
}>(status: number, data: {
    email: FormDataEntryValue | null;
    missing: boolean;
}): ActionFailure<...> (+1 overload)

Create an ActionFailure object.

@paramstatus The HTTP status code. Must be in the range 400-599.
@paramdata Data associated with the failure (e.g. validation errors)
fail
(400, { email: FormDataEntryValue | nullemail, missing: booleanmissing: true });
} if (const user: anyuser.password !== module "$lib/server/db"db.hash(const password: FormDataEntryValue | nullpassword)) { return
fail<{
    email: FormDataEntryValue | null;
    incorrect: boolean;
}>(status: number, data: {
    email: FormDataEntryValue | null;
    incorrect: boolean;
}): ActionFailure<...> (+1 overload)

Create an ActionFailure object.

@paramstatus The HTTP status code. Must be in the range 400-599.
@paramdata Data associated with the failure (e.g. validation errors)
fail
(400, { email: FormDataEntryValue | nullemail, incorrect: booleanincorrect: true });
} cookies: Cookies

Get or set cookies related to the current request

cookies
.
Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
    path: string;
}): void

Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.

The httpOnly and secure options are true by default (except on http://localhost, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.

You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children

@paramname the name of the cookie
@paramvalue the cookie value
@paramopts the options, passed directly to cookie.serialize. See documentation here
set
('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: string

Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute } . By default, the path is considered the “default path”.

path
: '/' });
if (url: URL

The requested URL.

url
.URL.searchParams: URLSearchParamssearchParams.URLSearchParams.has(name: string, value?: string): boolean

Returns a Boolean indicating if such a search parameter exists.

MDN Reference

has
('redirectTo')) {
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never

Redirect a request. When called during request handling, SvelteKit will return a redirect response. Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.

@paramstatus The HTTP status code. Must be in the range 300-308.
@paramlocation The location to redirect to.
@throwsRedirect This error instructs SvelteKit to redirect to the specified location.
@throwsError If the provided status is invalid.
redirect
(303, url.searchParams.get('redirectTo'));
} return { success: booleansuccess: true }; }, register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => { // TODO register the user } };
import { function fail(status: number): ActionFailure<undefined> (+1 overload)

Create an ActionFailure object.

@paramstatus The HTTP status code. Must be in the range 400-599.
fail
, function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never

Redirect a request. When called during request handling, SvelteKit will return a redirect response. Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.

@paramstatus The HTTP status code. Must be in the range 300-308.
@paramlocation The location to redirect to.
@throwsRedirect This error instructs SvelteKit to redirect to the specified location.
@throwsError If the provided status is invalid.
redirect
} from '@sveltejs/kit';
import * as module "$lib/server/db"db from '$lib/server/db'; import type {
type Actions = {
    [x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions
} from './$types';
export const
const actions: {
    login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
        email: FormDataEntryValue | null;
        missing: boolean;
    }> | ActionFailure<...> | {
        ...;
    }>;
    register: (event: RequestEvent<...>) => Promise<...>;
}
actions
= {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
    email: FormDataEntryValue | null;
    missing: boolean;
}> | ActionFailure<...> | {
    ...;
}>
login
: async ({ cookies: Cookies

Get or set cookies related to the current request

cookies
, request: Request

The original request object

request
, url: URL

The requested URL.

url
}) => {
const const data: FormDatadata = await request: Request

The original request object

request
.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email'); const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password'); const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValue | nullemail); if (!const user: anyuser) { return
fail<{
    email: FormDataEntryValue | null;
    missing: boolean;
}>(status: number, data: {
    email: FormDataEntryValue | null;
    missing: boolean;
}): ActionFailure<...> (+1 overload)

Create an ActionFailure object.

@paramstatus The HTTP status code. Must be in the range 400-599.
@paramdata Data associated with the failure (e.g. validation errors)
fail
(400, { email: FormDataEntryValue | nullemail, missing: booleanmissing: true });
} if (const user: anyuser.password !== module "$lib/server/db"db.hash(const password: FormDataEntryValue | nullpassword)) { return
fail<{
    email: FormDataEntryValue | null;
    incorrect: boolean;
}>(status: number, data: {
    email: FormDataEntryValue | null;
    incorrect: boolean;
}): ActionFailure<...> (+1 overload)

Create an ActionFailure object.

@paramstatus The HTTP status code. Must be in the range 400-599.
@paramdata Data associated with the failure (e.g. validation errors)
fail
(400, { email: FormDataEntryValue | nullemail, incorrect: booleanincorrect: true });
} cookies: Cookies

Get or set cookies related to the current request

cookies
.
Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
    path: string;
}): void

Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.

The httpOnly and secure options are true by default (except on http://localhost, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.

You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children

@paramname the name of the cookie
@paramvalue the cookie value
@paramopts the options, passed directly to cookie.serialize. See documentation here
set
('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: string

Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute } . By default, the path is considered the “default path”.

path
: '/' });
if (url: URL

The requested URL.

url
.URL.searchParams: URLSearchParamssearchParams.URLSearchParams.has(name: string, value?: string): boolean

Returns a Boolean indicating if such a search parameter exists.

MDN Reference

has
('redirectTo')) {
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never

Redirect a request. When called during request handling, SvelteKit will return a redirect response. Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.

@paramstatus The HTTP status code. Must be in the range 300-308.
@paramlocation The location to redirect to.
@throwsRedirect This error instructs SvelteKit to redirect to the specified location.
@throwsError If the provided status is invalid.
redirect
(303, url.searchParams.get('redirectTo'));
} return { success: booleansuccess: true }; }, register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => { // TODO register the user } } satisfies
type Actions = {
    [x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions
;

加载数据(Loading data)

¥Loading data

操作运行后,页面将重新渲染(除非发生重定向或意外错误),操作的返回值将作为 form 属性提供给页面。这意味着你页面的 load 函数将在操作完成后运行。

¥After an action runs, the page will be re-rendered (unless a redirect or an unexpected error occurs), with the action’s return value available to the page as the form prop. This means that your page’s load functions will run after the action completes.

请注意,handle 在调用操作之前运行,并且不会在 load 函数之前重新运行。这意味着,例如,如果你使用 handle 根据 cookie 填充 event.locals,则在操作中设置或删除 cookie 时必须更新 event.locals

¥Note that handle runs before the action is invoked, and does not rerun before the load functions. This means that if, for example, you use handle to populate event.locals based on a cookie, you must update event.locals when you set or delete the cookie in an action:

src/hooks.server
/** @type {import('@sveltejs/kit').Handle} */
export async function 
function handle(input: {
    event: RequestEvent;
    resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
}): MaybePromise<...>
@type{import('@sveltejs/kit').Handle}
handle
({ event: RequestEvent<Partial<Record<string, string>>, string | null>event, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve }) {
event: RequestEvent<Partial<Record<string, string>>, string | null>event.RequestEvent<Partial<Record<string, string>>, string | null>.locals: App.Locals

Contains custom data that was added to the request within the server handle hook.

locals
.
App.Locals.user: {
    name: string;
} | null
user
= await
function getUser(sessionid: string | undefined): {
    name: string;
}
getUser
(event: RequestEvent<Partial<Record<string, string>>, string | null>event.RequestEvent<Partial<Record<string, string>>, string | null>.cookies: Cookies

Get or set cookies related to the current request

cookies
.Cookies.get(name: string, opts?: CookieParseOptions): string | undefined

Gets a cookie that was previously set with cookies.set, or from the request headers.

@paramname the name of the cookie
@paramopts the options, passed directly to cookie.parse. See documentation here
get
('sessionid'));
return resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve(event: RequestEvent<Partial<Record<string, string>>, string | null>event); }
import type { 
type Handle = (input: {
    event: RequestEvent;
    resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
}) => MaybePromise<...>

The handle hook runs every time the SvelteKit server receives a request and determines the response. It receives an event object representing the request and a function called resolve, which renders the route and generates a Response. This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example).

Handle
} from '@sveltejs/kit';
export const const handle: Handlehandle:
type Handle = (input: {
    event: RequestEvent;
    resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
}) => MaybePromise<...>

The handle hook runs every time the SvelteKit server receives a request and determines the response. It receives an event object representing the request and a function called resolve, which renders the route and generates a Response. This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example).

Handle
= async ({ event: RequestEvent<Partial<Record<string, string>>, string | null>event, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve }) => {
event: RequestEvent<Partial<Record<string, string>>, string | null>event.RequestEvent<Partial<Record<string, string>>, string | null>.locals: App.Locals

Contains custom data that was added to the request within the server handle hook.

locals
.
App.Locals.user: {
    name: string;
} | null
user
= await
function getUser(sessionid: string | undefined): {
    name: string;
}
getUser
(event: RequestEvent<Partial<Record<string, string>>, string | null>event.RequestEvent<Partial<Record<string, string>>, string | null>.cookies: Cookies

Get or set cookies related to the current request

cookies
.Cookies.get(name: string, opts?: CookieParseOptions): string | undefined

Gets a cookie that was previously set with cookies.set, or from the request headers.

@paramname the name of the cookie
@paramopts the options, passed directly to cookie.parse. See documentation here
get
('sessionid'));
return resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve(event: RequestEvent<Partial<Record<string, string>>, string | null>event); };
src/routes/account/+page.server
/** @type {import('./$types').PageServerLoad} */
export function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
@type{import('./$types').PageServerLoad}
load
(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>event) {
return {
user: {
    name: string;
} | null
user
: event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>event.RequestEvent<Record<string, any>, string | null>.locals: App.Locals

Contains custom data that was added to the request within the server handle hook.

locals
.
App.Locals.user: {
    name: string;
} | null
user
}; } /** @satisfies {import('./$types').Actions} */ export const
const actions: {
    logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>;
}
@satisfies{import('./$types').Actions}
actions
= {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>logout: async (event: RequestEvent<Record<string, any>, string | null>event) => { event: RequestEvent<Record<string, any>, string | null>event.RequestEvent<Record<string, any>, string | null>.cookies: Cookies

Get or set cookies related to the current request

cookies
.
Cookies.delete(name: string, opts: CookieSerializeOptions & {
    path: string;
}): void

Deletes a cookie by setting its value to an empty string and setting the expiry date in the past.

You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children

@paramname the name of the cookie
@paramopts the options, passed directly to cookie.serialize. The path must match the path of the cookie you want to delete. See documentation here
delete
('sessionid', { path: string

Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute } . By default, the path is considered the “default path”.

path
: '/' });
event: RequestEvent<Record<string, any>, string | null>event.RequestEvent<Params extends Partial<Record<string, string>> = Partial<Record<string, string>>, RouteId extends string | null = string | null>.locals: App.Locals

Contains custom data that was added to the request within the server handle hook.

locals
.
App.Locals.user: {
    name: string;
} | null
user
= null;
} };
import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad, 
type Actions = {
    [x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions
} from './$types';
export const const load: PageServerLoadload: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>event) => { return {
user: {
    name: string;
} | null
user
: event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>event.RequestEvent<Record<string, any>, string | null>.locals: App.Locals

Contains custom data that was added to the request within the server handle hook.

locals
.
App.Locals.user: {
    name: string;
} | null
user
}; }; export const
const actions: {
    logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>;
}
actions
= {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>logout: async (event: RequestEvent<Record<string, any>, string | null>event) => { event: RequestEvent<Record<string, any>, string | null>event.RequestEvent<Record<string, any>, string | null>.cookies: Cookies

Get or set cookies related to the current request

cookies
.
Cookies.delete(name: string, opts: CookieSerializeOptions & {
    path: string;
}): void

Deletes a cookie by setting its value to an empty string and setting the expiry date in the past.

You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children

@paramname the name of the cookie
@paramopts the options, passed directly to cookie.serialize. The path must match the path of the cookie you want to delete. See documentation here
delete
('sessionid', { path: string

Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute } . By default, the path is considered the “default path”.

path
: '/' });
event: RequestEvent<Record<string, any>, string | null>event.RequestEvent<Params extends Partial<Record<string, string>> = Partial<Record<string, string>>, RouteId extends string | null = string | null>.locals: App.Locals

Contains custom data that was added to the request within the server handle hook.

locals
.
App.Locals.user: {
    name: string;
} | null
user
= null;
} } satisfies
type Actions = {
    [x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions
;

渐进增强(Progressive enhancement)

¥Progressive enhancement

在前面的部分中,我们构建了一个 /login 操作,该操作是 无需客户端 JavaScript 即可工作 — 而不是 fetch。这很好,但是当 JavaScript 可用时,我们可以逐步增强我们的表单交互以提供更好的用户体验。

¥In the preceding sections we built a /login action that works without client-side JavaScript — not a fetch in sight. That’s great, but when JavaScript is available we can progressively enhance our form interactions to provide a better user experience.

use:enhance

逐步增强表单的最简单方法是添加 use:enhance 操作:

¥The easiest way to progressively enhance a form is to add the use:enhance action:

src/routes/login/+page
<script>
	import { enhance } from '$app/forms';

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

<form method="POST" use:enhance>
<script lang="ts">
	import { enhance } from '$app/forms';
	import type { PageProps } from './$types';

	let { form }: PageProps = $props();
</script>

<form method="POST" use:enhance>

use:enhance 只能用于具有 method="POST" 并指向 +page.server.js 文件中定义的操作的表单。它不适用于 method="GET"method="GET" 是未指定方法的表单的默认设置。尝试在没有 method="POST" 的表单上使用 use:enhance 或发布到 +server.js 端点将导致错误。

¥[!NOTE] use:enhance can only be used with forms that have method="POST" and point to actions defined in a +page.server.js file. It will not work with method="GET", which is the default for forms without a specified method. Attempting to use use:enhance on forms without method="POST" or posting to a +server.js endpoint will result in an error.

是的,enhance 操作和 <form action> 都被称为 ‘action’,这有点令人困惑。这些文档内容丰富。抱歉。

¥[!NOTE] Yes, it’s a little confusing that the enhance action and <form action> are both called ‘action’. These docs are action-packed. Sorry.

如果没有参数,use:enhance 将模拟浏览器原生行为,只是没有整页重新加载。它将:

¥Without an argument, use:enhance will emulate the browser-native behaviour, just without the full-page reloads. It will:

  • 在成功或无效响应时更新 form 属性、page.formpage.status,但前提是操作位于你提交的同一页面上。例如,如果你的表单看起来像 <form action="/somewhere/else" ..>,则 form prop 和 page.form 状态将不会更新。这是因为在原生表单提交情况下,你将被重定向到操作所在的页面。如果你想以任何方式更新它们,请使用 applyAction

    ¥update the form property, page.form and page.status on a successful or invalid response, but only if the action is on the same page you’re submitting from. For example, if your form looks like <form action="/somewhere/else" ..>, the form prop and the page.form state will not be updated. This is because in the native form submission case you would be redirected to the page the action is on. If you want to have them updated either way, use applyAction

  • routes/about/+page.svelte

  • 在成功响应时使用 invalidateAll 使所有数据无效

    ¥invalidate all data using invalidateAll on a successful response

  • 在重定向响应上调用 goto

    ¥call goto on a redirect response

  • 如果发生错误,则渲染最近的 +error 边界

    ¥render the nearest +error boundary if an error occurs

  • 重置焦点 到适当的元素

    ¥reset focus to the appropriate element

自定义 use:enhance(Customising use:enhance)

¥Customising use:enhance

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

¥To customise the behaviour, you can provide a SubmitFunction that runs immediately before the form is submitted, and (optionally) returns a callback that runs with the ActionResult. Note that if you return a callback, the default behavior mentioned above is not triggered. To get it back, call 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,等等。

¥You can use these functions to show and hide loading UI, and so on.

如果你返回回调,你可能需要重现部分默认 use:enhance 行为,但不会在成功响应时使所有数据无效。你可以使用 applyAction 执行此操作:

¥If you return a callback, you may need to reproduce part of the default use:enhance behaviour, but without invalidating all data on a successful response. You can do so with applyAction:

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

	/** @type {import('./$types').PageProps} */
	let { form } = $props();
</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);
			}
		};
	}}
>
<script lang="ts">
	import { enhance, applyAction } from '$app/forms';
	import type { PageProps } from './$types';

	let { form }: PageProps = $props();
</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

¥The behaviour of applyAction(result) depends on result.type:

  • successfailure — 将 page.status 设置为 result.status 并将 formpage.form 更新为 result.data(无论你从哪里提交,与从 enhance 提交的 update 不同)

    ¥success, failure — sets page.status to result.status and updates form and page.form to result.data (regardless of where you are submitting from, in contrast to update from enhance)

  • redirect — 调用 goto(result.location, { invalidateAll: true })

    ¥redirect — calls goto(result.location, { invalidateAll: true })

  • error — 使用 result.error 渲染最近的 +error 边界

    ¥error — renders the nearest +error boundary with result.error

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

¥In all cases, focus will be reset.

自定义事件监听器(Custom event listener)

¥Custom event listener

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

¥We can also implement progressive enhancement ourselves, without use:enhance, with a normal event listener on the <form>:

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

	/** @type {import('./$types').PageProps} */
	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') {
			// rerun all `load` functions, following the successful update
			await invalidateAll();
		}

		applyAction(result);
	}
</script>

<form method="POST" onsubmit={handleSubmit}>
	<!-- content -->
</form>
<script lang="ts">
	import { invalidateAll, goto } from '$app/navigation';
	import { applyAction, deserialize } from '$app/forms';
	import type { PageProps } from './$types';
	import type { ActionResult } from '@sveltejs/kit';

	let { form }: PageProps = $props();

	async function handleSubmit(event: SubmitEvent & { currentTarget: EventTarget & HTMLFormElement}) {
		event.preventDefault();
		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" onsubmit={handleSubmit}>
	<!-- content -->
</form>

请注意,你需要先对响应进行 deserialize,然后再使用 $app/forms 中的相应方法进一步处理它。JSON.parse() 不够,因为表单操作 - lucia (auth) - 还支持返回 DateBigInt 对象。

¥Note that you need to deserialize the response before processing it further using the corresponding method from $app/forms. JSON.parse() isn’t enough because form actions - like load functions - also support returning Date or BigInt objects.

如果你在 +page.server.js 旁边有一个 +server.js,则默认情况下 fetch 请求将被路由到那里。要将 POST 转换为 +page.server.js 中的操作,请使用自定义 x-sveltekit-action 标头:

¥If you have a +server.js alongside your +page.server.js, fetch requests will be routed there by default. To POST to an action in +page.server.js instead, use the custom x-sveltekit-action header:

const const response: Responseresponse = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)fetch(this.action, {
	RequestInit.method?: string | undefined

A string to set request’s method.

method
: 'POST',
RequestInit.body?: BodyInit | null | undefined

A BodyInit object or null to set request’s body.

body
: data,
RequestInit.headers?: HeadersInit | undefined

A Headers object, an object literal, or an array of two-item arrays to set request’s headers.

headers
: {
'x-sveltekit-action': 'true' } });

替代方案(Alternatives)

¥Alternatives

表单操作是将数据发送到服务器的首选方式,因为它们可以逐步增强,但你也可以使用 +server.js 文件来公开(例如)JSON API。以下是这种交互可能的样子:

¥Form actions are the preferred way to send data to the server, since they can be progressively enhanced, but you can also use +server.js files to expose (for example) a JSON API. Here’s how such an interaction could look like:

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

<button onclick={rerun}>Rerun CI</button>
<script lang="ts">
	function rerun() {
		fetch('/api/ci', {
			method: 'POST'
		});
	}
</script>

<button onclick={rerun}>Rerun CI</button>
src/routes/api/ci/+server
/** @type {import('./$types').RequestHandler} */
export function function POST(): void
@type{import('./$types').RequestHandler}
POST
() {
// do something }
import type { 
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler
} from './$types';
export const const POST: RequestHandlerPOST:
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler
= () => {
// do something };

GET 与 POST(GET vs POST)

¥GET vs POST

正如我们所见,要调用表单操作,你必须使用 method="POST"

¥As we’ve seen, to invoke a form action you must use method="POST".

某些表单不需要将数据 POST 发送到服务器 — 例如搜索输入。对于这些,你可以使用 method="GET"(或者,完全不使用 method),SvelteKit 会将它们视为 <a> 元素,使用客户端路由而不是完整的页面导航:

¥Some forms don’t need to POST data to the server — search inputs, for example. For these you can use method="GET" (or, equivalently, no method at all), and SvelteKit will treat them like <a> elements, using the client-side router instead of a full page navigation:

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

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

¥Submitting this form will navigate to /search?q=... and invoke your load function but will not invoke an action. As with <a> elements, you can set the data-sveltekit-reload, data-sveltekit-replacestate, data-sveltekit-keepfocus and data-sveltekit-noscroll attributes on the <form> to control the router’s behaviour.

进一步阅读(Further reading)

¥Further reading

上一页 下一页