Form actions
一个 +page.server.js 文件可以导出 actions,这些操作允许您使用 POST 数据通过 <form> 元素发送到服务器。
当使用<form>时,客户端 JavaScript 是可选的,但您可以使用 JavaScript 轻松地渐进式增强您的表单交互,以提供最佳的用户体验。
默认操作
在最简单的情况下,一个页面声明了一个 默认动作:
/** @satisfies {import('./$types').Actions} */
export const const actions: {
default: (event: any) => Promise<void>;
}
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:
<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请求不应产生副作用。
我们也可以从其他页面调用操作(例如,如果根布局中的导航中有登录小部件)通过添加操作属性,指向页面:
<form method="POST" action="/login">
<!-- content -->
</form>命名动作
而不是一个 默认 动作,一个页面可以有它需要的任意多个命名动作:
/** @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: 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;调用命名操作时,添加一个以/字符为前缀的查询参数:
<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: CookiesGet or set cookies related to the current request
cookies }) {
const const user: anyuser = await module "$lib/server/db"db.getUserFromSession(cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.get: (name: string, opts?: CookieParseOptions) => string | undefinedGets a cookie that was previously set with cookies.set, or from the request headers.
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<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object.
request }) => {
const const data: FormDatadata = await request: RequestThe 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: CookiesGet 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
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies 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: CookiesGet or set cookies related to the current request
cookies }) => {
const const user: anyuser = await module "$lib/server/db"db.getUserFromSession(cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.get: (name: string, opts?: CookieParseOptions) => string | undefinedGets a cookie that was previously set with cookies.set, or from the request headers.
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: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object.
request }) => {
const const data: FormDatadata = await request: RequestThe 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: CookiesGet 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
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies 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;<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}[!旧版]
PageProps在 2.16.0 版本中添加。在早期版本中,您必须单独输入data和form属性:+page/** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ let {let data: anydata,let form: anyform } =function $props(): any namespace $props$props();Declares the props that a component accepts. Example:
let { optionalProp = 42, requiredProp, bindableProp = $bindable() }: { optionalProp?: number; requiredProps: string; bindableProp: boolean } = $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 namespace $props$props();Declares the props that a component accepts. Example:
let { optionalProp = 42, requiredProp, bindableProp = $bindable() }: { optionalProp?: number; requiredProps: string; bindableProp: boolean } = $props();在 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. Call when form submission fails.
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: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object.
request }) => {
const const data: FormDatadata = await request: RequestThe 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. Call when form submission fails.
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. Call when form submission fails.
email: FormDataEntryValueemail, incorrect: booleanincorrect: true });
}
cookies: CookiesGet or set cookies related to the current request
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
module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies 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”.
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. Call when form submission fails.
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: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object.
request }) => {
const const data: FormDatadata = await request: RequestThe 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. Call when form submission fails.
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. Call when form submission fails.
email: FormDataEntryValueemail, incorrect: booleanincorrect: true });
}
cookies: CookiesGet or set cookies related to the current request
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
module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies 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”.
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;[!注意] 注意,作为预防措施,我们只将电子邮件返回到页面,而不是密码。
<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。除此之外,结构完全由您决定。例如,如果您在页面上有多个表单,您可以使用一个form的form数据,通过一个id属性或类似的方式来区分它们。
重定向
重定向(和错误)与在加载中工作完全相同:
import { function fail(status: number): ActionFailure<undefined> (+1 overload)Create an ActionFailure object. Call when form submission fails.
fail, function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): neverRedirect 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.
Most common status codes:
303 See Other: redirect as a GET request (often used after a form POST request)
307 Temporary Redirect: redirect will keep the request method
308 Permanent Redirect: redirect will keep the request method, SEO will be transferred to the new page
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: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object.
request, url: URLThe requested URL.
url }) => {
const const data: FormDatadata = await request: RequestThe 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. Call when form submission fails.
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. Call when form submission fails.
fail(400, { email: FormDataEntryValue | nullemail, incorrect: booleanincorrect: true });
}
cookies: CookiesGet 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
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies 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: URLThe requested URL.
url.URL.searchParams: URLSearchParamssearchParams.URLSearchParams.has(name: string, value?: string): booleanReturns 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): neverRedirect 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.
Most common status codes:
303 See Other: redirect as a GET request (often used after a form POST request)
307 Temporary Redirect: redirect will keep the request method
308 Permanent Redirect: redirect will keep the request method, SEO will be transferred to the new page
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. Call when form submission fails.
fail, function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): neverRedirect 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.
Most common status codes:
303 See Other: redirect as a GET request (often used after a form POST request)
307 Temporary Redirect: redirect will keep the request method
308 Permanent Redirect: redirect will keep the request method, SEO will be transferred to the new page
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: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object.
request, url: URLThe requested URL.
url }) => {
const const data: FormDatadata = await request: RequestThe 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. Call when form submission fails.
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. Call when form submission fails.
fail(400, { email: FormDataEntryValue | nullemail, incorrect: booleanincorrect: true });
}
cookies: CookiesGet 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
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies 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: URLThe requested URL.
url.URL.searchParams: URLSearchParamssearchParams.URLSearchParams.has(name: string, value?: string): booleanReturns 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): neverRedirect 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.
Most common status codes:
303 See Other: redirect as a GET request (often used after a form POST request)
307 Temporary Redirect: redirect will keep the request method
308 Permanent Redirect: redirect will keep the request method, SEO will be transferred to the new page
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;加载数据
在动作执行后,页面将重新渲染(除非发生重定向或意外错误),动作的返回值将以 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.LocalsContains 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: CookiesGet or set cookies related to the current request
cookies.Cookies.get: (name: string, opts?: CookieParseOptions) => string | undefinedGets 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: 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.LocalsContains 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: CookiesGet or set cookies related to the current request
cookies.Cookies.get: (name: string, opts?: CookieParseOptions) => string | undefinedGets 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.LocalsContains 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: CookiesGet 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: stringSpecifies 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.LocalsContains 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.LocalsContains 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: CookiesGet 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: stringSpecifies 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.LocalsContains 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 操作:
<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="POST"的表单上使用use:enhance或向+server.js端点提交将导致错误。
[!注意] 是的,有点令人困惑,
增强操作和<form action>都被称为’操作’。这些文档充满了操作。抱歉。
无参数时,使用use:enhance将模拟浏览器原生行为,只是没有整个页面的刷新。它将:
- 更新
表单属性、page.form和page.status在成功或无效响应时,但仅当操作是在您提交的同一页面上时。例如,如果您的表单看起来像<form action="/somewhere/else" ..>,则表单属性和page.form状态将 不会 更新。这是因为在本机表单提交的情况下,您将被重定向到操作所在的页面。如果您想无论哪种情况都更新它们,请使用applyAction。 - 重置
<form>元素 - 使用
invalidateAll在成功响应中使所有数据无效 - 调用重定向响应中的
goto - 渲染最近的
+错误边界,如果发生错误 - 重置焦点到适当的元素
自定义使用:增强
要自定义行为,您可以在表单提交前提供一个立即运行的 SubmitFunction,并且(可选地)返回一个在 ActionResult 中运行的回调。
<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 等。
如果您返回一个回调,您将覆盖默认的提交后行为。要恢复,请调用update,它接受invalidateAll和reset参数,或者在对结果使用applyAction:
<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:
成功,失败— 将page.status设置为result.status,并更新form和page.form为result.data(无论您从哪里提交,与从enhance的update相比)重定向— 调用goto(result.location, { invalidateAll: true })错误— 以+错误边界渲染result.error
在所有情况下,焦点将被重置。
自定义事件监听器
我们也可以自行实现渐进增强,无需使用use:enhance,只需在<form>上添加一个正常的事件监听器:
<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>请注意,在进一步使用相应方法从 $app/forms 处理之前,您需要先 反序列化 响应。仅使用 JSON.parse() 不够,因为表单操作 - 如 load 函数 - 也支持返回 Date 或 BigInt 对象。
如果您旁边有一个+server.js和+page.server.js,默认情况下,fetch请求将被路由到那里。要向+page.server.js中的操作POST,请使用自定义x-sveltekit-action头:
const const response: Responseresponse = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)fetch(this.action, {
RequestInit.method?: string | undefinedA string to set request’s method.
method: 'POST',
RequestInit.body?: BodyInit | null | undefinedA BodyInit object or null to set request’s body.
body: data,
RequestInit.headers?: HeadersInit | undefinedA 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><script lang="ts">
function rerun() {
fetch('/api/ci', {
method: 'POST'
});
}
</script>
<button onclick={rerun}>Rerun CI</button>/** @type {import('./$types').RequestHandler} */
export function function POST(): voidPOST() {
// 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
我们已看到,要调用表单操作,您必须使用method="POST"。
一些表单不需要向服务器发送POST数据——例如搜索输入。对于这些,您可以使用method="GET"(或者,等价地,根本不设置method),SvelteKit 会将它们视为<a>元素,使用客户端路由而不是完整页面导航:
<form action="/search">
<label>
Search
<input name="q">
</label>
</form>提交此表单将导航到 /search?q=... 并调用您的加载函数,但不会调用操作。与 <a> 元素一样,您可以在 <form> 上设置 data-sveltekit-reload、data-sveltekit-replacestate、data-sveltekit-keepfocus 和 data-sveltekit-noscroll 属性来控制路由器的行为。
进一步阅读
Edit this page on GitHub llms.txt