formタグとFormDataの活用
前回の記事では、僕たちは multipart/form-data のリクエスト形式と、それが解決しようとしている課題について紹介した。今回の記事では、実際の開発で直面する問題や活用方法について解説する。
具体的には、本記事ではフォームの活用に関する以下の内容を扱う:
<form/>タグの裏側で何が行われているか- JavaScriptにおけるFormDataの活用
- FormDataと
fetchの組み合わせ - JavaScriptでファイルアップロードを操作する方法
改めてformタグについて
HTMLのformタグには、実はブラウザが実装してくれている細かな処理がたくさんあり、開発者はそれを見落としがちだ。以下の例を見てみよう:
<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="text" name="name" />
<input type="file" name="file" />
<button>
Submit
</button>
</form>
JavaScriptのコードが一切ない状態でも、Submitボタンを押せばブラウザが以下の処理を自動で行ってくれる:
- input内のnameおよびfileフィールドをシリアライズする
- POSTメソッドで
Content-Type: multipart/form-dataのHTTPリクエストを送信する - ファイルを読み込み、リクエスト内容に追加する(ファイルが存在する場合)
SPA(シングルページアプリケーション)やフロントエンドフレームワークがまだ流行っていなかった頃は、formフォームにデータを入力して送信し、別のページへリダイレクトするのが一般的な手法だった。しかし、入力するデータが増えたり、一部のエリアだけを更新したい(例えばコメント欄など)場合、毎回ページ全体を再読み込みするのはユーザー体験としてあまり良くない。そのため、AjaxでAPIを叩き、JavaScriptでデータを動的に更新する手法が徐々に広まっていった。

動的更新の手法は確かにユーザー体験を向上させたが、優れたフォーム設計を実現するには多くの細部を考慮する必要がある:
- エラーハンドリング
- 状態遷移
- データの保持
- アクセシビリティ(Accessibility)
多くのケースでは、どれか1つの要素でも疎かになると、ユーザーはむしろ単にページ全体を更新する通常のフォームタグで操作したほうがマシだと感じてしまう。管理画面のようなアプリケーションにおいては、<form> フォームによるページ全体の更新を採用することで多くの開発時間を削減できることが多く、ブラウザの組み込み機能に頼るほうがかえって安定して動作することさえある。
JavaScriptにおけるFormDataの活用
FormData は、開発者がkey/valueのような操作を扱いやすくするためのインターフェースを定義しており、最も一般的な用途はフォーム処理だ。FormData の宣言は次のように書ける:
const formData = new FormData()
// key value
formData.append('name', 'Kalan');
form要素を FormData に渡すと、フォーム内に入力された情報が自動的に FormData としてシリアライズされる:
<form id="form" enctype="multipart/form-data" action="/upload" method="POST">
<input type="text" name="name" />
<input type="file" name="file" />
<button>Submit</button>
</form>
<script>
const formData = new FormData(document.getElementById('form'));
formData.get('name'); // 取得目前 input 的值
formData.get('file'); // 取得目前的檔案
</script>

さらに、FormDataをfetchのbodyに設定すると、ブラウザが自動的に multipart/form-data の形式で送信してくれる:
const formData = new FormData();
formData.append('name', 'Kalan');
formData.append('file', new File(['Hello World'], 'file.txt', { type: 'text/plain' }))
fetch('/upload', {
method: 'POST',
body: formData
})
上記のJavaScriptコードを実行してNetworkリクエストを観察すると、Content-Typeを特に指定していなくても、ブラウザが自動的に multipart/form-data として送信してくれ、form dataのシリアライズもブラウザによって行われていることがわかる。

まとめ
この2つの記事を通じて、multipart/form-data の活用法と実際の実装方法を解説した。1本目の記事では、Content-Disposition の意味、boundaryの用途、そしてmultipart/form-dataリクエストの構造について解説した。2本目となる本記事では、実務においてformとFormDataをどのように活用できるか、そしてJavaScriptを通してFormDataの処理やファイルアップロードを行う方法を説明した。
サーバー側から見れば、Webページでのファイルアップロードという動作も1つのHTTPリクエストに過ぎない。そのため、サーバー側はヘッダーの情報や multipart/form-data で定義されたフォーマットに基づいてデータをパースして初めて、ファイル内容を正しく取得できる。通常、これらのパース処理はフレームワークによって隠蔽されているが、ここで僕が特に強調しておきたいのは、Web上でファイルを転送することに不思議な魔法などなく、裏側では依然としてHTTPリクエストに基づいているということだ。
関連記事
- Three.js で僕の部屋を表現する 僕が React Three Fiber を使って実際の自分の部屋をブラウザ上に再現し、実世界のオブジェクトを目録に見立て、空間の記憶を通してここ数年の生活と仕事について語った話。
- フロントエンドで画像を扱う際に注意すべきこと Jake Archibaldの記事を起点に、現代のレスポンシブ画像の書き方を整理する。なぜwidth/heightを付ける必要があるのか、CSSのaspect-ratioはいつ使うべきか、AVIFとWebPの選び方、そしてpicture/source/srcsetを使ったモバイル向け画像の切り替えについて。
- CSS field-sizing — たった1行のCSSでフォーム要素を自動リサイズする かつてtextareaの自動高さ調整は、JavaScriptでscrollHeightを監視するしかなかった。しかしCSSのfield-sizing: contentなら、わずか1行で代替でき、textarea、input、selectに対応している。本記事では従来のやり方のペインポイントと、field-sizingの使い方をまとめる。
- リンクの下線をもっと見栄え良くする:text-underline-offset デフォルトでは下線と文字が近すぎて、このスタイルを好まないデザイナーもいるし、僕自身もあまり綺麗ではないと感じていた。