核心概念
表單動作
在 GitHub 上編輯此頁面+page.server.js
檔案可以匯出動作,讓您可以使用 <form>
元素將資料 POST
到伺服器。
使用 <form>
時,客戶端 JavaScript 是選用的,但您可以輕鬆地使用 JavaScript 逐步增強您的表單互動,以提供最佳使用者體驗。
預設動作永久連結
在最簡單的情況下,頁面會宣告一個 default
動作
ts
/** @type {import('./$types').Actions} */export constactions = {default : async (event ) => {// TODO log the user in}};
ts
import type {Actions } from './$types';export constactions = {default : async (event ) => {// TODO log the user in},} satisfiesActions ;
若要從 /login
頁面呼叫此動作,只要加入一個 <form>
,不需要 JavaScript
<form method="POST">
<label>
Email
<input name="email" type="email">
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
</form>
如果有人按一下按鈕,瀏覽器會透過 POST
要求將表單資料傳送至伺服器,執行預設動作。
動作總是使用
POST
要求,因為GET
要求不應產生副作用。
我們也可以從其他頁面呼叫動作(例如,如果根佈局的導覽列中有登入小工具),方法是加入 action
屬性,指向該頁面
<form method="POST" action="/login">
<!-- content -->
</form>
命名動作永久連結
頁面可以有任意數量的命名動作,而不只一個 default
動作
/** @type {import('./$types').Actions} */
export const actions = {
default: async (event) => {
login: async (event) => {
// TODO log the user in
},
register: async (event) => {
// TODO register the user
}
};
若要呼叫命名動作,請加入一個查詢參數,其名稱前面加上 /
字元
<form method="POST" action="?/register">
<form method="POST" action="/login?/register">
除了 action
屬性之外,我們可以使用按鈕上的 formaction
屬性,將相同的表單資料 POST
到與父 <form>
不同的動作。
<form method="POST">
<form method="POST" action="?/login">
<label>
Email
<input name="email" type="email">
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
<button formaction="?/register">Register</button>
</form>
我們無法在命名動作旁邊有預設動作,因為如果你在沒有重新導向的情況下
POST
到命名動作,查詢參數會保留在 URL 中,這表示下一個預設POST
會從之前的命名動作開始。
動作的解剖永久連結
每個動作都會收到一個 RequestEvent
物件,讓你能夠使用 request.formData()
讀取資料。在處理請求之後(例如透過設定 cookie 讓使用者登入),動作可以使用資料回應,這些資料會透過對應頁面上的 form
屬性,以及在下次更新之前透過 $page.form
在整個應用程式中提供。
ts
/** @type {import('./$types').PageServerLoad} */export async functionCannot find name 'db'.2304Cannot find name 'db'.load ({cookies }) {constCannot find name 'db'.2304Cannot find name 'db'.user = awaitdb .getUserFromSession ( cookies .get ('sessionid'));return {user };}/** @type {import('./$types').Actions} */export constactions = {login : async ({cookies ,request }) => {constdata = awaitrequest .formData ();constdata .get ('email');constpassword =data .get ('password');constuser = awaitdb .getUser (cookies .set ('sessionid', awaitdb .createSession (user ), {path : '/' });return {success : true };},register : async (event ) => {// TODO register the user}};
ts
import type {PageServerLoad ,Actions } from './$types';Cannot find name 'db'.2304Cannot find name 'db'.export constCannot find name 'db'.2304Cannot find name 'db'.load :PageServerLoad = async ({cookies }) => {constuser = awaitdb .getUserFromSession (cookies .get ('sessionid'));return {user };};export constactions = {login : async ({cookies ,request }) => {constdata = awaitrequest .formData ();constdata .get ('email');constpassword =data .get ('password');constuser = awaitdb .getUser (cookies .set ('sessionid', awaitdb .createSession (user ), {path : '/' });return {success : true };},register : async (event ) => {// TODO register the user},} satisfiesActions ;
<script>
/** @type {import('./$types').PageData} */
export let data;
/** @type {import('./$types').ActionData} */
export let form;
</script>
{#if form?.success}
<!-- this message is ephemeral; it exists because the page was rendered in
response to a form submission. it will vanish if the user reloads -->
<p>Successfully logged in! Welcome back, {data.user.name}</p>
{/if}
<script lang="ts">
import type { PageData, ActionData } from './$types';
export let data: PageData;
export let form: ActionData;
</script>
{#if form?.success}
<!-- this message is ephemeral; it exists because the page was rendered in
response to a form submission. it will vanish if the user reloads -->
<p>Successfully logged in! Welcome back, {data.user.name}</p>
{/if}
驗證錯誤永久連結
如果請求無法因為資料無效而處理,你可以將驗證錯誤(以及先前提交的表單值)傳回給使用者,以便他們可以重試。fail
函數讓你傳回 HTTP 狀態碼(通常在驗證錯誤的情況下為 400 或 422),以及資料。狀態碼可透過 $page.status
取得,而資料可透過 form
取得。
import { fail } from '@sveltejs/kit';
/** @type {import('./$types').Actions} */
export const actions = {
login: async ({ cookies, request }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
if (!email) {
return fail(400, { email, missing: true });
}
const user = await db.getUser(email);
if (!user || user.password !== hash(password)) {
return fail(400, { email, incorrect: true });
}
cookies.set('sessionid', await db.createSession(user), { path: '/' });
return { success: true };
},
register: async (event) => {
// TODO register the user
}
};
請注意,作為預防措施,我們只會將電子郵件傳回頁面,而不是密碼。
<form method="POST" action="?/login">
{#if form?.missing}<p class="error">The email field is required</p>{/if}
{#if form?.incorrect}<p class="error">Invalid credentials!</p>{/if}
<label>
Email
<input name="email" type="email">
<input name="email" type="email" value={form?.email ?? ''}>
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
<button formaction="?/register">Register</button>
</form>
傳回的資料必須可以序列化為 JSON。除此之外,結構完全取決於你。例如,如果你在頁面上有多個表單,你可以使用 id
屬性或類似方式,來區分傳回的 form
資料所指的 <form>
。
重新導向永久連結
重新導向(和錯誤)的工作方式與 load
中完全相同。
import { fail, redirect } from '@sveltejs/kit';
/** @type {import('./$types').Actions} */
export const actions = {
login: async ({ cookies, request, url }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
const user = await db.getUser(email);
if (!user) {
return fail(400, { email, missing: true });
}
if (user.password !== hash(password)) {
return fail(400, { email, incorrect: true });
}
cookies.set('sessionid', await db.createSession(user), { path: '/' });
if (url.searchParams.has('redirectTo')) {
redirect(303, url.searchParams.get('redirectTo'));
}
return { success: true };
},
register: async (event) => {
// TODO register the user
}
};
載入資料永久連結
在動作執行之後,頁面將會重新呈現(除非發生重新導向或意外錯誤),動作的回傳值會作為 form
道具提供給頁面。這表示你的頁面 load
函數會在動作完成後執行。
請注意,handle
會在動作呼叫之前執行,且不會在 load
函式之前再次執行。這表示,例如,如果您使用 handle
根據 cookie 填入 event.locals
,您必須在動作中設定或刪除 cookie 時更新 event.locals
ts
/** @type {import('@sveltejs/kit').Handle} */export async functionhandle ({event ,resolve }) {event .locals .user = awaitgetUser (event .cookies .get ('sessionid'));returnresolve (event );}
ts
import type {Handle } from '@sveltejs/kit';export consthandle :Handle = async ({event ,resolve }) => {event .locals .user = awaitgetUser (event .cookies .get ('sessionid'));returnresolve (event );};
ts
/** @type {import('./$types').PageServerLoad} */export functionload (event ) {return {user :event .locals .user };}/** @type {import('./$types').Actions} */export constactions = {logout : async (event ) => {event .cookies .delete ('sessionid', {path : '/' });event .locals .user = null;}};
ts
import type {PageServerLoad ,Actions } from './$types';export constload :PageServerLoad = (event ) => {return {user :event .locals .user ,};};export constactions = {logout : async (event ) => {event .cookies .delete ('sessionid', {path : '/' });event .locals .user = null;},} satisfiesActions ;
漸進式增強永久連結
在前面的區段中,我們建立了一個 /login
動作,它可以在沒有用戶端 JavaScript 的情況下運作,完全不需要 fetch
。這很棒,但當 JavaScript 可用 時,我們可以漸進式增強我們的表單互動,以提供更好的使用者體驗。
use:enhance永久連結
漸進式增強表單最簡單的方法是新增 use:enhance
動作
<script>
import { enhance } from '$app/forms';
/** @type {import('./$types').ActionData} */
export let form;
</script>
<form method="POST" use:enhance>
沒錯,
enhance
動作和<form action>
都稱為「動作」,這有點令人困惑。這些文件充滿了動作。抱歉。
use:enhance
在沒有參數的情況下,將模擬瀏覽器的原生行為,只是不會重新載入整頁。它會
- 在成功或無效的回應中更新
form
屬性、$page.form
和$page.status
,但前提是動作與您提交的頁面在同一個頁面上。例如,如果您的表單看起來像<form action="/somewhere/else" ..>
,則form
和$page
不會 更新。這是因為在原生表單提交案例中,您會被重新導向到動作所在的頁面。如果您希望無論如何都更新它們,請使用applyAction
- 重設
<form>
元素 - 在成功的回應中使用
invalidateAll
使所有資料失效 - 在重新導向回應中呼叫
goto
- 如果發生錯誤,則呈現最近的
+error
邊界 - 將焦點重設 到適當的元素
自訂 use:enhance永久連結
若要自訂行為,你可以提供一個在表單提交前立即執行的 SubmitFunction
,並(選擇性地)傳回一個與 ActionResult
一起執行的回呼。請注意,如果你傳回一個回呼,則上述提到的預設行為將不會觸發。若要取回該行為,請呼叫 update
。
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel, submitter }) => {
// `formElement` is this `<form>` element
// `formData` is its `FormData` object that's about to be submitted
// `action` is the URL to which the form is posted
// calling `cancel()` will prevent the submission
// `submitter` is the `HTMLElement` that caused the form to be submitted
return async ({ result, update }) => {
// `result` is an `ActionResult` object
// `update` is a function which triggers the default logic that would be triggered if this callback wasn't set
};
}}
>
你可以使用這些函數來顯示和隱藏載入 UI 等。
如果你傳回一個回呼,你可能需要重現預設 use:enhance
行為的一部分,但不必在成功回應時使所有資料失效。你可以使用 applyAction
執行此操作。
<script>
import { enhance, applyAction } from '$app/forms';
/** @type {import('./$types').ActionData} */
export let form;
</script>
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel }) => {
return async ({ result }) => {
// `result` is an `ActionResult` object
if (result.type === 'redirect') {
goto(result.location);
} else {
await applyAction(result);
}
};
}}
>
applyAction(result)
的行為取決於 result.type
success
、failure
— 將$page.status
設定為result.status
,並將form
和$page.form
更新為result.data
(無論你從何處提交,這與enhance
中的update
相反)redirect
— 呼叫goto(result.location, { invalidateAll: true })
error
— 使用result.error
呈現最近的+error
邊界
在所有情況下,焦點將會重設。
自訂事件監聽器永久連結
我們也可以在沒有 use:enhance
的情況下,使用 <form>
上的正常事件監聽器,自行實作漸進式增強。
<script>
import { invalidateAll, goto } from '$app/navigation';
import { applyAction, deserialize } from '$app/forms';
/** @type {import('./$types').ActionData} */
export let form;
/** @type {any} */
let error;
/** @param {{ currentTarget: EventTarget & HTMLFormElement}} event */
async function handleSubmit(event) {
const data = new FormData(event.currentTarget);
const response = await fetch(event.currentTarget.action, {
method: 'POST',
body: data
});
/** @type {import('@sveltejs/kit').ActionResult} */
const result = deserialize(await response.text());
if (result.type === 'success') {
// rerun all `load` functions, following the successful update
await invalidateAll();
}
applyAction(result);
}
</script>
<form method="POST" on:submit|preventDefault={handleSubmit}>
<!-- content -->
</form>
<script lang="ts">
import { invalidateAll, goto } from '$app/navigation';
import { applyAction, deserialize } from '$app/forms';
import type { ActionData } from './$types';
import type { ActionResult } from '@sveltejs/kit';
export let form: ActionData;
let error: any;
async function handleSubmit(event: { currentTarget: EventTarget & HTMLFormElement }) {
const data = new FormData(event.currentTarget);
const response = await fetch(event.currentTarget.action, {
method: 'POST',
body: data,
});
const result: ActionResult = deserialize(await response.text());
if (result.type === 'success') {
// rerun all `load` functions, following the successful update
await invalidateAll();
}
applyAction(result);
}
</script>
<form method="POST" on:submit|preventDefault={handleSubmit}>
<!-- content -->
</form>
請注意,在使用 $app/forms
中的對應方法進一步處理回應之前,你需要對回應進行 deserialize
。JSON.parse()
不夠用,因為表單動作(如 load
函數)也支援傳回 Date
或 BigInt
物件。
如果你在 +page.server.js
旁有一個 +server.js
,則 fetch
要求會預設路由到那裡。若要改為 POST
到 +page.server.js
中的動作,請使用自訂 x-sveltekit-action
標頭
const response = await fetch(this.action, {
method: 'POST',
body: data,
headers: {
'x-sveltekit-action': 'true'
}
});
替代方案永久連結
表單動作是將資料傳送至伺服器的方式,因為它們可以漸進式增強,但你也可以使用 +server.js
檔案來公開(例如)JSON API。以下是如何執行此類互動
<script>
function rerun() {
fetch('/api/ci', {
method: 'POST'
});
}
</script>
<button on:click={rerun}>Rerun CI</button>
<script lang="ts">
function rerun() {
fetch('/api/ci', {
method: 'POST',
});
}
</script>
<button on:click={rerun}>Rerun CI</button>
ts
/** @type {import('./$types').RequestHandler} */export functionPOST () {// do something}
ts
import type {RequestHandler } from './$types';export constPOST :RequestHandler = () => {// do something};
GET 與 POST永久連結
如我們所見,若要呼叫表單動作,你必須使用 method="POST"
。
有些表單不需要 POST
資料到伺服器,例如搜尋輸入。對於這些表單,你可以使用 method="GET"
(或等效地,完全不使用 method
),而 SvelteKit 會將它們視為 <a>
元素,使用用戶端路由器,而不是完整的頁面導覽
<form action="/search">
<label>
Search
<input name="q">
</label>
</form>
提交此表單將導航至 /search?q=...
並呼叫您的載入函式,但不會呼叫動作。與 <a>
元素一樣,您可以設定 <form>
上的 data-sveltekit-reload
、data-sveltekit-replacestate
、data-sveltekit-keepfocus
和 data-sveltekit-noscroll
屬性,以控制路由器的行為。