JavaScript | DOM 操作:フォーム操作 – フォームデータ送信(FormData)

JavaScript JavaScript
スポンサーリンク

FormData とは何か

FormData は、フォームの入力値を「送信用のペア(name → value)」としてまとめるための標準APIです。ファイルも含めて簡単に扱え、fetch などの非同期送信でそのまま使えます。ここが重要です:FormData は“現在の入力状態”を収集し、checkbox 未チェックはキーなし、multiple な select は getAll で複数値を取得する――この挙動を理解しておくとデータの取り扱いが正確になります。


基本の使い方(収集・参照・送信)

現在のフォーム値を収集する

<form id="f">
  <input name="email" value="a@example.com">
  <input name="agree" type="checkbox" value="yes">
  <select name="tag" multiple>
    <option value="js" selected>JavaScript</option>
    <option value="ui" selected>UI/UX</option>
  </select>
  <button>送信</button>
</form>
<script>
  const fd = new FormData(document.getElementById("f"));
  console.log(fd.get("email"));    // "a@example.com"
  console.log(fd.has("agree"));    // checkbox 未チェックなら false(キーなし)
  console.log(fd.getAll("tag"));   // ["js", "ui"](multiple は getAll)
</script>
HTML

ここが重要です:get は単一値、getAll は複数値。チェックの有無や未選択の判定は has/get の戻りで行い、空文字と“キーなし”を混同しないのがコツです。

fetch でそのまま送る(multipart/form-data)

<script>
  async function submitForm(form) {
    const fd = new FormData(form);
    const res = await fetch(form.action || "/api/submit", {
      method: form.method || "POST",
      body: fd, // Content-Type はブラウザが自動設定(boundary 含む)
    });
    if (res.ok) alert("送信に成功しました");
    else alert("送信に失敗しました");
  }
  document.getElementById("f").addEventListener("submit", (e) => {
    e.preventDefault();
    submitForm(e.target);
  });
</script>
HTML

ここが重要です:body に FormData を渡すと、ブラウザが適切な multipart/form-data ヘッダーを自動で付けます。自分で Content-Type を設定しないのが正解です。


ファイル送信(File / Blob の扱い)

file input から送る

<form id="avatarForm">
  <input name="avatar" type="file" accept="image/*">
  <button>アップロード</button>
</form>
<script>
  avatarForm.addEventListener("submit", async (e) => {
    e.preventDefault();
    const fd = new FormData(avatarForm);
    const res = await fetch("/upload", { method: "POST", body: fd });
    alert(res.ok ? "アップロード成功" : "アップロード失敗");
  });
</script>
HTML

ここが重要です:files[0] を自分で触らずとも、FormData は file input の選択を自動で収集します。複数ファイルなら input に multiple を付ければ複数送信されます。

手動で Blob/File を追加する

<script>
  const fd = new FormData();
  const blob = new Blob(["hello"], { type: "text/plain" });
  fd.append("note", blob, "note.txt"); // filename を付けるとファイル扱い
</script>
HTML

ここが重要です:append は値だけでなく Blob/File も追加できます。filename を指定するとサーバー側でファイル名として受け取れます。


値の追加・更新・削除(append / set / delete)

追加(同じキーを複数持たせる)

<script>
  const fd = new FormData();
  fd.append("tag", "js");
  fd.append("tag", "ui");
  console.log(fd.getAll("tag")); // ["js", "ui"]
</script>
HTML

ここが重要です:append は“同名キーを増やす”。複数選択や複数ファイルの表現に向いています。

上書き(常に一つにしたい)

<script>
  const fd = new FormData();
  fd.set("page", "1");  // 既存があれば置換、無ければ追加
  fd.set("page", "2");  // "2" に上書き(get は "2")
</script>
HTML

ここが重要です:set は“そのキーを一つだけ”に保ちたい場合の最短ルートです。

削除(キーごと消す)

<script>
  fd.delete("tag"); // "tag" のエントリをすべて削除
</script>
HTML

ここが重要です:delete はキー全削除。再構成前に一度消してから append/set する設計が安全です。


JSON が必要なAPIへの対応(変換・設計の注意点)

FormData をオブジェクトへ変換して JSON 送信

<script>
  const fd = new FormData(document.getElementById("f"));
  const obj = Object.fromEntries(fd.entries()); // キーの先頭値だけを取り込む
  const res = await fetch("/api/json", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(obj),
  });
</script>
HTML

ここが重要です:Object.fromEntries は“各キーの先頭の値のみ”変換します。multiple の値や同名キーが複数ある場合は、必要に応じて getAll で配列化してから組み立てます。

checkbox 未チェックの扱い(キーが無い問題)

<script>
  const fd = new FormData(f);
  const agreed = fd.has("agree"); // true/false を明示のブールに変換
  const payload = { ...Object.fromEntries(fd), agree: agreed };
</script>
HTML

ここが重要です:未チェックはキーがありません。サーバーがブールを期待するなら、has() で明示的に true/false を付与する設計が堅牢です。


バリデーションと送信の流れ(checkValidity と submit 制御)

送信前に組み込み検証を通す

<script>
  const form = document.getElementById("f");
  form.addEventListener("submit", async (e) => {
    e.preventDefault();
    if (!form.checkValidity()) {
      form.reportValidity(); // バブル表示で案内
      return;
    }
    const fd = new FormData(form);
    const res = await fetch(form.action, { method: form.method, body: fd });
    if (res.ok) form.reset();
  });
</script>
HTML

ここが重要です:checkValidity → reportValidity の流れで“正しい値だけ送る”。失敗時は送信を止め、成功時に reset で初期化するだけで UX が安定します。


実践例(依存セレクトと一括送信、進捗と二重送信防止)

依存セレクトの値を含めて送る

<form id="place">
  <select name="pref" required>
    <option value="">都道府県</option>
    <option value="tokyo">東京都</option>
  </select>
  <select name="ward" required></select>
  <button>送信</button>
</form>
<script>
  const WARDS = { tokyo: [{ text:"江東区", value:"koto" }, { text:"渋谷区", value:"shibuya" }] };
  const pref = place.pref, ward = place.ward;
  function fillWard(list) {
    ward.length = 0;
    for (const i of list) ward.add(new Option(i.text, i.value));
  }
  pref.addEventListener("change", () => fillWard(WARDS[pref.value] || []));
  fillWard([]);

  place.addEventListener("submit", async (e) => {
    e.preventDefault();
    if (!place.checkValidity()) return place.reportValidity();
    const fd = new FormData(place);
    const res = await fetch("/api/place", { method:"POST", body: fd });
    alert(res.ok ? "送信成功" : "送信失敗");
  });
</script>
HTML

ここが重要です:選択肢を動的に構築しても、FormData は“その時点の選択”を正しく収集します。送信前に必須チェックを通しておけば、未選択のまま送る事故を防げます。

進行中表示と二重送信防止

<script>
  place.addEventListener("submit", async (e) => {
    e.preventDefault();
    if (!place.checkValidity()) return place.reportValidity();
    const btn = e.submitter; btn.disabled = true; btn.textContent = "送信中…";
    try {
      const fd = new FormData(place);
      const res = await fetch("/api/place", { method:"POST", body: fd });
      alert(res.ok ? "完了" : "失敗");
    } finally {
      btn.disabled = false; btn.textContent = "送信";
    }
  });
</script>
HTML

ここが重要です:送信中はボタン無効化と文言変更で状況を明示。連打による二重送信を防ぎ、体験を落ち着かせます。


よくある落とし穴と回避策

checkbox 未チェックは“キーがない”ため、単純な Object.fromEntries だけだと抜け落ちます。必要なら has() でブール化して補完します。multiple な select を get で読むと先頭しか取れないので、必ず getAll を使います。FormData を fetch に渡すときは Content-Type を自分で設定しないこと。ブラウザが boundary を含めて正しく付与します。ファイルを JSON に含めることはできないため、ファイル送信は FormData のままにするか、別のアップロードAPIを用意します。送信前の検証を省くと、空や不正値で API を叩いてしまい、サーバー側の負荷とユーザーのやり直しが増えます。checkValidity/reportValidity の流れを必ず挟んでください。


まとめ

FormData は「フォームの現在値を送信用のマップへ直列化する」ための最短手段です。get と getAll の違い、checkbox 未チェックの“キーなし”、ファイルをそのまま送れる点を押さえれば、fetch への非同期送信がシンプルに書けます。追加・更新・削除は append/set/delete、JSON が必要なら Object.fromEntries と getAll で丁寧に組み立てる。送信前は checkValidity/reportValidity で正しい値だけ送る――この基本を徹底すれば、初心者でも短いコードで堅実なフォーム送信が実装できます。

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