表单操作
一个 +page.server.js
文件可以导出 操作,允许你使用 <form>
元素将数据 POST
到服务器。
当使用 <form>
时,客户端 JavaScript 是可选的,但你可以轻松地通过 JavaScript 渐进增强你的表单交互,以提供最佳的用户体验。
默认操作
在最简单的情况下,页面声明一个 default
操作
/** @satisfies {import('./$types').Actions} */
export const const actions: {
default: (event: any) => Promise<void>;
}
actions = {
default: (event: any) => Promise<void>
default: async (event: any
event) => {
// 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
<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
操作
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: (event: any) => Promise<void>;
register: (event: any) => Promise<void>;
}
actions = {
default: async (event) => {
login: (event: any) => Promise<void>
login: async (event: any
event) => {
// TODO log the user in
},
register: (event: any) => Promise<void>
register: async (event: any
event) => {
// 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;
要调用命名操作,请添加一个查询参数,其名称以 /
字符为前缀
<form method="POST" action="?/register">
<form method="POST" action="/login?/register">
除了 action
属性之外,我们还可以使用按钮上的 formaction
属性将相同的表单数据 POST
到与父 <form>
不同的操作。
<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
(在整个应用中,直到下次更新)中可用的数据进行响应。
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>>
load({ cookies: Cookies
Get or set cookies related to the current request
cookies }) {
const const user: any
user = 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.
get('sessionid'));
return { user: any
user };
}
/** @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<...>;
}
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: FormData
data = await request: Request
The original request object
request.Body.formData(): Promise<FormData>
formData();
const const email: FormDataEntryValue | null
email = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('email');
const const password: FormDataEntryValue | null
password = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('password');
const const user: any
user = await module "$lib/server/db"
db.getUser(const email: FormDataEntryValue | null
email);
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 https://127.0.0.1, 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
set('sessionid', await module "$lib/server/db"
db.createSession(const user: any
user), { 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: boolean
success: 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: PageServerLoad
load: 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: any
user = 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.
get('sessionid'));
return { user: any
user };
};
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: FormData
data = await request: Request
The original request object
request.Body.formData(): Promise<FormData>
formData();
const const email: FormDataEntryValue | null
email = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('email');
const const password: FormDataEntryValue | null
password = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('password');
const const user: any
user = await module "$lib/server/db"
db.getUser(const email: FormDataEntryValue | null
email);
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 https://127.0.0.1, 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
set('sessionid', await module "$lib/server/db"
db.createSession(const user: any
user), { 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: boolean
success: 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;
<script>
/** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */
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 { PageData, ActionData } from './$types';
let { data, form }: { data: PageData, form: ActionData } = $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}
传统模式
在 Svelte 4 中,你将使用
export let data
和export let form
来声明属性
验证错误
如果由于数据无效而无法处理请求,你可以将验证错误(以及之前提交的表单值)返回给用户,以便他们可以重试。fail
函数允许你返回一个 HTTP 状态代码(通常是 400 或 422,在验证错误的情况下)以及数据。状态代码可通过 $page.status
获取,数据可通过 form
获取
import { function fail(status: number): ActionFailure<undefined> (+1 overload)
Create an ActionFailure
object.
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<...>;
}
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: FormData
data = await request: Request
The original request object
request.Body.formData(): Promise<FormData>
formData();
const const email: FormDataEntryValue | null
email = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('email');
const const password: FormDataEntryValue | null
password = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('password');
if (!const email: FormDataEntryValue | null
email) {
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.
fail(400, { email: string | null
email, missing: boolean
missing: true });
}
const const user: any
user = await module "$lib/server/db"
db.getUser(const email: FormDataEntryValue
email);
if (!const user: any
user || const user: any
user.password !== module "$lib/server/db"
db.hash(const password: FormDataEntryValue | null
password)) {
return fail<{
email: FormDataEntryValue;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue;
incorrect: boolean;
}): ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: FormDataEntryValue
email, incorrect: boolean
incorrect: 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 https://127.0.0.1, 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
set('sessionid', await module "$lib/server/db"
db.createSession(const user: any
user), { 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: boolean
success: 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.
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: FormData
data = await request: Request
The original request object
request.Body.formData(): Promise<FormData>
formData();
const const email: FormDataEntryValue | null
email = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('email');
const const password: FormDataEntryValue | null
password = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('password');
if (!const email: FormDataEntryValue | null
email) {
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.
fail(400, { email: string | null
email, missing: boolean
missing: true });
}
const const user: any
user = await module "$lib/server/db"
db.getUser(const email: FormDataEntryValue
email);
if (!const user: any
user || const user: any
user.password !== module "$lib/server/db"
db.hash(const password: FormDataEntryValue | null
password)) {
return fail<{
email: FormDataEntryValue;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue;
incorrect: boolean;
}): ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: FormDataEntryValue
email, incorrect: boolean
incorrect: 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 https://127.0.0.1, 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
set('sessionid', await module "$lib/server/db"
db.createSession(const user: any
user), { 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: boolean
success: 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;
请注意,作为预防措施,我们只将电子邮件返回到页面 - 而不是密码。
<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>
。
重定向
重定向(和错误)的工作方式与 load
中完全相同
import { function fail(status: number): ActionFailure<undefined> (+1 overload)
Create an ActionFailure
object.
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.
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<...>;
}
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: FormData
data = await request: Request
The original request object
request.Body.formData(): Promise<FormData>
formData();
const const email: FormDataEntryValue | null
email = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('email');
const const password: FormDataEntryValue | null
password = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('password');
const const user: any
user = await module "$lib/server/db"
db.getUser(const email: FormDataEntryValue | null
email);
if (!const user: any
user) {
return fail<{
email: FormDataEntryValue | null;
missing: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
missing: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: FormDataEntryValue | null
email, missing: boolean
missing: true });
}
if (const user: any
user.password !== module "$lib/server/db"
db.hash(const password: FormDataEntryValue | null
password)) {
return fail<{
email: FormDataEntryValue | null;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
incorrect: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: FormDataEntryValue | null
email, incorrect: boolean
incorrect: 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 https://127.0.0.1, 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
set('sessionid', await module "$lib/server/db"
db.createSession(const user: any
user), { 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: URLSearchParams
searchParams.URLSearchParams.has(name: string, value?: string): boolean
Returns a Boolean indicating if such a search parameter exists.
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.
redirect(303, url: URL
The requested URL.
url.URL.searchParams: URLSearchParams
searchParams.URLSearchParams.get(name: string): string | null
Returns the first value associated to the given search parameter.
get('redirectTo'));
}
return { success: boolean
success: 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.
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.
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: FormData
data = await request: Request
The original request object
request.Body.formData(): Promise<FormData>
formData();
const const email: FormDataEntryValue | null
email = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('email');
const const password: FormDataEntryValue | null
password = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('password');
const const user: any
user = await module "$lib/server/db"
db.getUser(const email: FormDataEntryValue | null
email);
if (!const user: any
user) {
return fail<{
email: FormDataEntryValue | null;
missing: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
missing: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: FormDataEntryValue | null
email, missing: boolean
missing: true });
}
if (const user: any
user.password !== module "$lib/server/db"
db.hash(const password: FormDataEntryValue | null
password)) {
return fail<{
email: FormDataEntryValue | null;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
incorrect: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: FormDataEntryValue | null
email, incorrect: boolean
incorrect: 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 https://127.0.0.1, 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
set('sessionid', await module "$lib/server/db"
db.createSession(const user: any
user), { 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: URLSearchParams
searchParams.URLSearchParams.has(name: string, value?: string): boolean
Returns a Boolean indicating if such a search parameter exists.
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.
redirect(303, url.searchParams.get('redirectTo'));
}
return { success: boolean
success: 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;
加载数据
操作运行后,页面将重新渲染(除非发生重定向或意外错误),并且操作的返回值作为 form
属性提供给页面。这意味着你的页面的 load
函数将在操作完成后运行。
请注意,handle
在调用操作之前运行,并且在 load
函数之前不会重新运行。这意味着,例如,如果你使用 handle
基于 cookie 填充 event.locals
,则必须在操作中设置或删除 cookie 时更新 event.locals
/** @type {import('@sveltejs/kit').Handle} */
export async function function handle(input: {
event: RequestEvent;
resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
}): MaybePromise<...>
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.
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: Handle
handle: 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.
get('sessionid'));
return resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>
resolve(event: RequestEvent<Partial<Record<string, string>>, string | null>
event);
};
/** @type {import('./$types').PageServerLoad} */
export function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
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>;
}
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
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: PageServerLoad
load: 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
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;
渐进增强
在前面的章节中,我们构建了一个 /login
操作,该操作 无需客户端 JavaScript 即可工作 - 没有 fetch
。这很好,但是当 JavaScript 可用时,我们可以渐进增强表单交互以提供更好的用户体验。
use:enhance
渐进增强表单的最简单方法是添加 use:enhance
操作
<script>
import { enhance } from '$app/forms';
/** @type {{ form: import('./$types').ActionData }} */
let { form } = $props();
</script>
<form method="POST" use:enhance>
<script lang="ts">
import { enhance } from '$app/forms';
import type { ActionData } from './$types';
let { form }: { form: ActionData } = $props();
</script>
<form method="POST" use:enhance>
use:enhance
只能与具有method="POST"
的表单一起使用。它不适用于method="GET"
,这是没有指定方法的表单的默认值。尝试在没有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 {{ form: import('./$types').ActionData }} */
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 { ActionData } from './$types';
let { form }: { form: ActionData } = $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
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 {{ form: import('./$types').ActionData }} */
let { form } = $props();
/** @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" onsubmit|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';
let { form }: { form: ActionData } = $props();
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" onsubmit|preventDefault={handleSubmit}>
<!-- content -->
</form>
请注意,你需要在使用 $app/forms
中相应的方法进一步处理响应之前对其进行 反序列化
。JSON.parse()
不够,因为表单操作 - 就像 load
函数一样 - 也支持返回 Date
或 BigInt
对象。
如果你在 +page.server.js
旁边有一个 +server.js
,fetch
请求将默认路由到那里。要改为 POST
到 +page.server.js
中的操作,请使用自定义 x-sveltekit-action
标头
const const response: Response
response = 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'
}
});
替代方案
表单操作是将数据发送到服务器的首选方式,因为它们可以渐进增强,但你也可以使用 +server.js
文件公开(例如)JSON API。以下是这种交互可能的样子
<script>
function rerun() {
fetch('/api/ci', {
method: 'POST'
});
}
</script>
<button onclick={rerun}>Rerun CI</button>
/** @type {import('./$types').RequestHandler} */
export function function POST(): void
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: RequestHandler
POST: 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
正如我们所见,要调用表单操作,必须使用 method="POST"
。
有些表单不需要将数据 POST
到服务器 - 例如搜索输入。对于这些,你可以使用 method="GET"
(或等效地,不使用 method
),SvelteKit 将将其视为 <a>
元素,使用客户端路由器而不是完整页面导航
<form action="/search">
<label>
Search
<input name="q">
</label>
</form>
提交此表单将导航到 /search?q=...
并调用你的 load 函数,但不会调用操作。与 <a>
元素一样,你可以在 <form>
上设置 data-sveltekit-reload
、data-sveltekit-replacestate
、data-sveltekit-keepfocus
和 data-sveltekit-noscroll
属性来控制路由器的行为。