Svelte入門:SvelteKitでのフォームアクション -Vol.5-

スポンサーリンク
Svelte入門:SvelteKitでのフォームアクション -Vol.5- 用語解説
Svelte入門:SvelteKitでのフォームアクション -Vol.5-
この記事は約9分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

今日は、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は、ブラウザのネイティブな動作をエミュレートしますが、ページ全体の再読み込みを行いません。具体的には以下の動作を行います:

  1. 成功または無効な応答時にformプロパティ、$page.form$page.statusを更新します。ただし、アクションが同じページ上にある場合のみです。
  2. <form>要素をリセットします。
  3. 成功応答時にinvalidateAllを使用してすべてのデータを無効化します。
  4. リダイレクト応答時にgotoを呼び出します。
  5. エラー発生時に最も近い+error境界をレンダリングします。
  6. 適切な要素にフォーカスをリセットします。

注意点として、フォームが<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 によって異なります。

  1. successまたはfailureの場合:
    • $page.statusresult.statusに設定します。
    • form$page.formresult.dataで更新します。
    • 注意点:enhanceupdateと異なり、どこからフォームを送信しているかに関わらず更新が行われます。
  2. redirectの場合:
    • goto(result.location, { invalidateAll: true })を呼び出します。
  3. 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でのフォームアクションについて解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました