`+page.server.js` ファイルは _actions_ をエクスポートできます。これによって、`
` 要素を使用することでサーバーにデータを `POST` することができます。 `` を使用する場合、クライアントサイドの JavaScript はオプションですが、JavaScript によって form のインタラクションを簡単にプログレッシブに強化(_progressively enhance_)することができ、最高のユーザーエクスペリエンスを提供することができます。 ## Default actions 最もシンプルなケースでは、ページは `default` の action を宣言します: ```js /// file: src/routes/login/+page.server.js /** @type {import('./$types').Actions} */ export const actions = { default: async (event) => { // TODO log the user in } }; ``` `/login` ページからこの action を呼び出すには、`` を追加します。JavaScript は必要ありません: ```svelte
``` もし誰かがボタンをクリックしたら、ブラウザは form のデータを `POST` リクエストでサーバーに送信し、デフォルトの action が実行されます。 > [!NOTE] Action は常に `POST` リクエストを使用します。`GET` リクエストには決して副作用があってはならないからです。 また、`action` 属性を追加し、リクエスト先のページを指し示すことで、他のページから action を呼び出すこともできます (例えば、最上位のレイアウト(root layout)にある nav にログイン用の widget がある場合): ```html /// file: src/routes/+layout.svelte
``` ## Named actions 単一の `default` の action の代わりに、名前付きの action (named action) を必要なだけ持つことができます: ```js /// file: src/routes/login/+page.server.js /** @satisfies {import('./$types').Actions} */ export const actions = { --- default: async (event) => {--- +++ login: async (event) => {+++ // TODO log the user in }, +++ register: async (event) => { // TODO register the user }+++ }; ``` 名前付きの action (named action) を呼び出すには、クエリパラメータに `/` を接頭辞に付与したその action の名前を追加します: ```svelte
``` ```svelte ``` `action` 属性と同じように、button の `formaction` 属性を使用することができ、こうすると親の `` とは別の action に同じ form のデータを `POST` することができます: ```svelte /// file: src/routes/login/+page.svelte ++++++
``` > [!NOTE] 名前付き action (named action) の隣にデフォルトの action を置くことはできません。なぜなら リダイレクト無しで名前付き action (named action) に POST をすると、クエリパラメータが URL に保持され、それ以降デフォルトの POST をしようとしても以前 POST した名前付き action (named action) を通ってしまうからです。 ## action の構造 action はそれぞれ `RequestEvent` オブジェクトを受け取って、`request.formData()` でデータを読み込むことができます。リクエスト (例えば、cookie をセットしてユーザーをログインさせるなど) を処理したあと、action は次の更新まで、対応するページでは `form` プロパティで、アプリ全体では `page.form` で利用可能なデータで応答することができます。 ```js /// file: src/routes/login/+page.server.js // @filename: ambient.d.ts declare module '$lib/server/db'; // @filename: index.js // ---cut--- import * as db from '$lib/server/db'; /** @type {import('./$types').PageServerLoad} */ export async function load({ cookies }) { const user = await db.getUserFromSession(cookies.get('sessionid')); return { user }; } /** @satisfies {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'); const user = await db.getUser(email); cookies.set('sessionid', await db.createSession(user), { path: '/' }); return { success: true }; }, register: async (event) => { // TODO register the user } }; ``` ```svelte {#if form?.success}

Successfully logged in! Welcome back, {data.user.name}

{/if} ``` > [!LEGACY] > `PageProps` was added in 2.16.0. In earlier versions, you had to type the `data` and `form` properties individually: > ```js > /// file: +page.svelte > /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ > let { data, form } = $props(); > ``` > > In Svelte 4, you'd use `export let data` and `export let form` instead to declare properties. ### Validation errors 無効なデータが原因でリクエストが処理できなかった場合、再試行できるようにするために、直前に送信した form の値とともに validation error をユーザーに返すことができます。`fail` 関数は、HTTP ステータスコード (通常、validation error の場合は 400 か 422) をデータとともに返します。ステータスコードは `page.status` から使用することができ、data は `form` から使用することができます: ```js /// file: src/routes/login/+page.server.js // @filename: ambient.d.ts declare module '$lib/server/db'; // @filename: index.js // ---cut--- +++import { fail } from '@sveltejs/kit';+++ import * as db from '$lib/server/db'; /** @satisfies {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 !== db.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 } }; ``` > [!NOTE] 念のため、password は返さず、email のみをページに返していることにご注意ください。 ```svelte /// file: src/routes/login/+page.svelte
+++ {#if form?.missing}

The email field is required

{/if} {#if form?.incorrect}

Invalid credentials!

{/if}+++
``` 戻り値は JSON としてシリアライズ可能でなければなりません。その上で、構造は完全にあなた次第です。例えば、もしページに複数の form がある場合、返された `form` データがどの `
` を参照しているかを `id` プロパティなどで区別することができます。 ### Redirects redirect (と error) は [`load`](load#Redirects) のそれと同じように機能します: ```js // @errors: 2345 /// file: src/routes/login/+page.server.js // @filename: ambient.d.ts declare module '$lib/server/db'; // @filename: index.js // ---cut--- import { fail, +++redirect+++ } from '@sveltejs/kit'; import * as db from '$lib/server/db'; /** @satisfies {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 !== db.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 } }; ``` ## Loading data action の実行後、そのページは (リダイレクトや予期せぬエラーが発生しない限り) 再レンダリングされ、action の戻り値が `form` プロパティとしてそのページで使用できるようになります。つまり、ページの `load` 関数は、action が完了したあとに実行されるということです。 `handle` は action が呼び出される前に実行され、`load` 関数より前に再実行されることはないことに注意してください。つまり、例えば `handle` を使用して cookie を元に `event.locals` に値を入れる場合、action で cookie を設定したり削除したりするときは `event.locals` を更新しなければなりません: ```js /// file: src/hooks.server.js // @filename: ambient.d.ts declare namespace App { interface Locals { user: { name: string; } | null } } // @filename: global.d.ts declare global { function getUser(sessionid: string | undefined): { name: string; }; } export {}; // @filename: index.js // ---cut--- /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { event.locals.user = await getUser(event.cookies.get('sessionid')); return resolve(event); } ``` ```js /// file: src/routes/account/+page.server.js // @filename: ambient.d.ts declare namespace App { interface Locals { user: { name: string; } | null } } // @filename: index.js // ---cut--- /** @type {import('./$types').PageServerLoad} */ export function load(event) { return { user: event.locals.user }; } /** @satisfies {import('./$types').Actions} */ export const actions = { logout: async (event) => { event.cookies.delete('sessionid', { path: '/' }); event.locals.user = null; } }; ``` ## Progressive enhancement 前のセクションでは [クライアントサイドの JavaScriptなしで動作する](https://kryogenix.org/code/browser/everyonehasjs.html) `/login` action を構築しました — `fetch` は見当たりません。これは素晴らしいことですが、JavaScript が利用可能な場合は、より良いユーザーエクスペリンスを提供するために form のインタラクションをプログレッシブに強化 (progressively enhance) することができます。 ### use:enhance form をプログレッシブに強化する最も簡単な方法は、`use:enhance` action を追加することです: ```svelte /// file: src/routes/login/+page.svelte ``` > [!NOTE] `use:enhance` can only be used with forms that have `method="POST"` and point to actions defined in a `+page.server.js` file. It will not work with `method="GET"`, which is the default for forms without a specified method. Attempting to use `use:enhance` on forms without `method="POST"` or posting to a `+server.js` endpoint will result in an error. > [!NOTE] ええ、`enhance` action と `` をどちらも 'action' と呼んでいて、少し紛らわしいですよね。このドキュメントは action でいっぱいです。申し訳ありません。 引数が無い場合、`use:enhance` は、ブラウザネイティブの動作を、フルページリロードを除いてエミュレートします。それは: - action が送信元のページと同じ場所にある場合に限り、成功レスポンスまたは不正なレスポンスに応じて、`form` プロパティと `page.form` と `page.status` を更新します。例えば、`` というようなフォームの場合、`form` プロパティと `page.form` state は更新されません。これは、ネイティブのフォーム送信では action があるページにリダイレクトされるからです。どちらにしても更新させたい場合は、[`applyAction`](#Progressive-enhancement-Customising-use:enhance) を使用してください - `` 要素をリセットします - 成功レスポンスの場合は `invalidateAll` で全てのデータを無効化・最新化(invalidate)します - リダイレクトレスポンスの場合は `goto` を呼び出します - エラーが発生した場合はもっとも近くにある `+error` 境界をレンダリングします - 適切な要素に [フォーカスをリセット](accessibility#Focus-management) します ### use:enhance をカスタマイズする この挙動をカスタマイズするために、form が送信される直前に実行される `SubmitFunction` 関数を提供することができます。そして (オプションで) `ActionResult` を引数に取るコールバックを返すことができます。もしコールバックを返す場合、上述のデフォルトの動作はトリガーされません。元に戻すには、`update` を呼び出してください。 ```svelte { // `formElement` はこの `` 要素です // `formData` は送信される予定の `FormData` オブジェクトです // `action` はフォームが POST される URL です // `cancel()` を呼び出すと送信(submission)を中止します // `submitter` は、フォームの送信を実行した `HTMLElement` です return async ({ result, update }) => { // `result` は `ActionResult` オブジェクトです // `update` は、このコールバックが設定されていない場合に起動されるデフォルトのロジックを起動する関数です }; }} > ``` これらの関数を、ロード中の UI (loading UI) を表示したり隠したりすることなどに使用できます。 コールバックを返す場合は `use:enhance` のデフォルトの動作の一部を再現させる必要があるかもしれませんが、成功レスポンスで全てのデータを無効化・最新化(invalidate)する必要はありません。このような場合には `applyAction` を使用するとよいでしょう: ```svelte /// file: src/routes/login/+page.svelte { return async ({ result }) => { // `result` は `ActionResult` オブジェクトです +++ 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` — もっとも近くにある `+error` 境界を `result.error` でレンダリングします いずれの場合でも、[フォーカスはリセットされます](accessibility#Focus-management)。 ### Custom event listener `use:enhance` ではなく、`` の通常のイベントリスナーを使うことで、ご自身でプログレッシブ・エンハンスメント(progressive enhancement)を実装することもできます: ```svelte
``` 処理を進める前に、`$app/forms` の `deserialize` でレスポンスをデシリアライズする必要があることにご注意ください。`JSON.parse()` では不十分です。なぜなら、例えば `load` 関数のような form action は、`Date` や `BigInt` オブジェクトも戻り値としてサポートしているからです。 もし `+page.server.js` と `+server.js` のどちらも存在する場合、デフォルトでは、`fetch` リクエストは `+server.js` のほうにルーティングされます。`+page.server.js` の action に `POST` をするには、カスタムの `x-sveltekit-action` ヘッダーを使用します: ```js const response = await fetch(this.action, { method: 'POST', body: data, +++ headers: { 'x-sveltekit-action': 'true' }+++ }); ``` ## Alternatives サーバーにデータを送信する方法として、プログレッシブな強化(progressively enhance)を行うことができるため Form actions は望ましい方法ですが、[`+server.js`](routing#server) ファイルを使用して (例えば) JSON API を公開することもできます。それは例えばこのように行います: ```svelte ``` ```js // @errors: 2355 1360 2322 /// file: src/routes/api/ci/+server.js /** @type {import('./$types').RequestHandler} */ export function POST() { // do something } ``` ## GET vs POST これまで見てきたように、フォームアクションを使うには、`method="POST"` を使用する必要があります。 サーバーにデータを `POST` する必要がないフォームもあるでしょう — 例えば検索入力(search inputs)です。これに対応するには `method="GET"` (または、`method` を全く書かないのも同等です) を使うことができ、そして SvelteKit はそれを `` 要素のように扱い、フルページナビゲーションの代わりにクライアントサイドルーターを使用します。: ```html
``` この form を送信すると `/search?q=...` に移動して load 関数が実行されますが、action は実行されません。`
` 要素と同じように、[`data-sveltekit-reload`](link-options#data-sveltekit-reload) 属性、 [`data-sveltekit-replacestate`](link-options#data-sveltekit-replacestate) 属性、[`data-sveltekit-keepfocus`](link-options#data-sveltekit-keepfocus) 属性、 [`data-sveltekit-noscroll`](link-options#data-sveltekit-noscroll) 属性を `
` に設定することができ、ルーターの挙動をコントロールすることができます。 ## その他の参考資料 - [Tutorial: Forms](/tutorial/kit/the-form-element)