こんにちは。よっしーです(^^)
今日は、SvelteKitでのフォームアクションについて解説しています。
背景
SvelteKitでのフォームアクションについて調査する機会がありましたので、その時の内容を備忘として記事に残しました。
SvelteKitでのフォームアクションについて
+page.server.js
ファイルでは「アクション」をエクスポートできます。これにより、<form>
要素を使用してサーバーにデータをPOST
することが可能になります。<form>
を使用する際、クライアントサイドのJavaScriptは任意ですが、JavaScriptを使って簡単にフォームの操作を段階的に強化し、最高のユーザー体験を提供することができます。
段階的な強化
以前の記事では、クライアントサイドのJavaScriptを使用せずに動作する/login
アクションを構築しました。fetch
を一切使用していません。これは素晴らしいことですが、JavaScriptが利用可能な場合、フォームの操作を段階的に強化してより良いユーザー体験を提供することができます。
use:enhance
フォームを段階的に強化する最も簡単な方法は、 use:enhance アクションを追加することです。
// src/routes/login/+page.svelte
<script>
import { enhance } from '$app/forms';
/** @type {import('./$types').ActionData} */
export let form;
</script>
<form method="POST" use:enhance>
エンハンスアクションと<フォームアクション>が両方とも「アクション」と呼ばれているのは少し混乱しますが、ご了承ください。
引数なしのuse:enhance
は、ブラウザのネイティブな動作をエミュレートしますが、ページ全体の再読み込みを行いません。具体的には以下の動作を行います:
- 成功または無効な応答時に
form
プロパティ、$page.form
、$page.status
を更新します。ただし、アクションが同じページ上にある場合のみです。 <form>
要素をリセットします。- 成功応答時に
invalidateAll
を使用してすべてのデータを無効化します。 - リダイレクト応答時に
goto
を呼び出します。 - エラー発生時に最も近い
+error
境界をレンダリングします。 - 適切な要素にフォーカスをリセットします。
注意点として、フォームが<form action="/somewhere/else" ..>
のように別のページにアクションを持つ場合、form
と$page
は更新されません。これは、ネイティブのフォーム送信の場合、アクションのあるページにリダイレクトされるためです。常に更新したい場合はapplyAction
を使用します。
use:enhanceのカスタマイズ
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 を使用して行うことができます。
// src/routes/login/+page.svelte
<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
境界をレンダリングします。
すべての場合において、フォーカスがリセットされます。
このapplyAction
の動作は、フォーム送信後の様々なシナリオに対応するために設計されています。例えば、成功時のデータ更新、エラー時の適切な表示、リダイレクトの処理などを柔軟に行うことができます。
カスタムイベントリスナー
use:enhance を使用せずに、<form> 上の通常のイベント リスナーを使用して、プログレッシブ エンハンスメントを自分で実装することもできます。
// src/routes/login/+page.svelte
<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>
レスポンスを処理する前に、$app/formsの対応するメソッドを使用してデシリアライズする必要があります。JSON.parse()だけでは不十分です。フォームアクション(ロード関数のようなもの)は、DateやBigIntオブジェクトを返すこともサポートしています。
+server.jsが+page.server.jsと一緒にある場合、フェッチリクエストはデフォルトで+server.jsにルーティングされます。+page.server.jsのアクションにPOSTするには、カスタムヘッダーx-sveltekit-actionを使用します。
const response = await fetch(this.action, {
method: 'POST',
body: data,
headers: {
'x-sveltekit-action': 'true'
}
});
おわりに
今日は、 SvelteKitでのフォームアクションについて解説しました。
何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント