2021 年 6 月 22 日

FormData

本章節探討如何傳送 HTML 表單:有或沒有檔案、額外欄位等等。

FormData 物件可以協助處理。正如你可能猜到的,它是用來表示 HTML 表單資料的物件。

建構函式為

let formData = new FormData([form]);

如果提供了 HTML form 元素,它會自動擷取其欄位。

FormData 的特別之處在於,例如 fetch 等網路方法可以接受 FormData 物件作為主體。它會編碼並以 Content-Type: multipart/form-data 傳送出去。

從伺服器觀點來看,這看起來像一般的表單提交。

傳送簡單表單

讓我們先傳送一個簡單表單。

正如你所見,這幾乎是一行式

<form id="formElem">
  <input type="text" name="name" value="John">
  <input type="text" name="surname" value="Smith">
  <input type="submit">
</form>

<script>
  formElem.onsubmit = async (e) => {
    e.preventDefault();

    let response = await fetch('/article/formdata/post/user', {
      method: 'POST',
      body: new FormData(formElem)
    });

    let result = await response.json();

    alert(result.message);
  };
</script>

在此範例中,未提供伺服器程式碼,因為這超出我們的範圍。伺服器接受 POST 要求並回覆「已儲存使用者」。

FormData 方法

我們可以使用方法修改 FormData 中的欄位

  • formData.append(name, value) – 新增一個具有指定 namevalue 的表單欄位,
  • formData.append(name, blob, fileName) – 新增一個欄位,就像 <input type="file">,第三個引數 fileName 設定檔名(不是表單欄位名稱),就像使用者檔案系統中的檔名一樣,
  • formData.delete(name) – 移除具有指定 name 的欄位,
  • formData.get(name) – 取得具有指定 name 的欄位值,
  • formData.has(name) – 如果存在具有指定 name 的欄位,則傳回 true,否則傳回 false

技術上允許表單具有多個具有相同 name 的欄位,因此多次呼叫 append 會新增更多具有相同名稱的欄位。

還有一個方法 set,其語法與 append 相同。不同之處在於 .set 會移除所有具有指定 name 的欄位,然後新增一個新欄位。因此,它確保只有一個具有此 name 的欄位,其餘部分就像 append

  • formData.set(name, value),
  • formData.set(name, blob, fileName).

我們也可以使用 for..of 迴圈反覆處理 formData 欄位

let formData = new FormData();
formData.append('key1', 'value1');
formData.append('key2', 'value2');

// List key/value pairs
for(let [name, value] of formData) {
  alert(`${name} = ${value}`); // key1 = value1, then key2 = value2
}

傳送包含檔案的表單

表單總是作為 Content-Type: multipart/form-data 傳送,此編碼允許傳送檔案。因此,<input type="file"> 欄位也會傳送,類似於一般的表單提交。

以下是此類表單的範例

<form id="formElem">
  <input type="text" name="firstName" value="John">
  Picture: <input type="file" name="picture" accept="image/*">
  <input type="submit">
</form>

<script>
  formElem.onsubmit = async (e) => {
    e.preventDefault();

    let response = await fetch('/article/formdata/post/user-avatar', {
      method: 'POST',
      body: new FormData(formElem)
    });

    let result = await response.json();

    alert(result.message);
  };
</script>

傳送包含 Blob 資料的表單

正如我們在章節 Fetch 中所見,傳送動態產生的二進位資料(例如影像)作為 Blob 非常容易。我們可以直接將其提供為 fetch 參數 body

然而,在實務上,通常比較方便將影像作為表單的一部分傳送,而不是單獨傳送,並加上其他欄位,例如「名稱」和其他元資料。

此外,伺服器通常更適合接受多部分編碼表單,而不是原始二進位資料。

此範例使用 FormData 將來自 <canvas> 的影像與其他一些欄位一起作為表單提交

<body style="margin:0">
  <canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas>

  <input type="button" value="Submit" onclick="submit()">

  <script>
    canvasElem.onmousemove = function(e) {
      let ctx = canvasElem.getContext('2d');
      ctx.lineTo(e.clientX, e.clientY);
      ctx.stroke();
    };

    async function submit() {
      let imageBlob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));

      let formData = new FormData();
      formData.append("firstName", "John");
      formData.append("image", imageBlob, "image.png");

      let response = await fetch('/article/formdata/post/image-form', {
        method: 'POST',
        body: formData
      });
      let result = await response.json();
      alert(result.message);
    }

  </script>
</body>

請注意如何新增影像 Blob

formData.append("image", imageBlob, "image.png");

這與在表單中使用 <input type="file" name="image"> 相同,且訪客從其檔案系統提交名為 "image.png" (第 3 個引數) 的檔案,其資料為 imageBlob (第 2 個引數)。

伺服器會讀取表單資料和檔案,就像一般的表單提交一樣。

摘要

FormData 物件用於擷取 HTML 表單,並使用 fetch 或其他網路方法提交表單。

我們可以從 HTML 表單建立 new FormData(form),或是在沒有表單的情況下建立一個物件,然後使用下列方法新增欄位:

  • formData.append(name, value)
  • formData.append(name, blob, fileName)
  • formData.set(name, value)
  • formData.set(name, blob, fileName)

讓我們在此注意兩個特殊情況:

  1. set 方法會移除具有相同名稱的欄位,而 append 則不會。這是兩者之間唯一的差異。
  2. 若要傳送檔案,需要使用 3 個引數的語法,最後一個引數是檔案名稱,通常會從 <input type="file"> 的使用者檔案系統取得。

其他方法包括:

  • formData.delete(name)
  • formData.get(name)
  • formData.has(name)

就這樣!

教學課程地圖

留言

留言前請先閱讀以下內容…
  • 如果您有改善建議,請 提交 GitHub 問題 或提出プル要求,而非留言。
  • 如果您無法理解文章中的某個部分,請詳細說明。
  • 若要插入幾行程式碼,請使用 <code> 標籤,若要插入多行程式碼,請將其包覆在 <pre> 標籤中,若要插入超過 10 行程式碼,請使用沙盒 (plnkrjsbincodepen…)