半熟前端

軟體工程師 / 台灣人 / 在前端的路上一邊探索其他領域的可能性

前端

form 標籤與 FormData 的應用

form 標籤與 FormData 的應用

上一篇文章中,我們介紹了 multipart/form-data 的請求格式以及它想解決的問題。這篇文章中會以實際開發上遇到的問題跟應用做解說。

具體來說,這篇文章會包含下列幾個有關於表單應用的部分:

  • <form/> 標籤背後做了哪些事
  • FormData 在 JavaScript 當中的應用
  • 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 請求
  • 讀取檔案並加入到請求內容中(如果檔案存在)

在單頁應用、前端框架還不流行的時候,用 form 表單填寫資料送出,然後重新導向到其他頁面是常見的做法。但是當填寫的資料變多,或是只有部分區域需要更新(例如留言等)時每次都要整頁更新對使用者來說體驗並不好,所以逐漸衍生出透過 ajax 打 API,並用 JavaScript 動態更新資料的做法。

Desktop - 1

雖然動態更新的方式的確改善了使用者體驗,但是要實現一個好的表單設計卻需要考慮許多細節:

  • 錯誤處理
  • 狀態轉換
  • 資料保存
  • Accessibility

很多時候只要一個環節沒有做好,使用者反而還寧願用單純整頁更新的表單標籤來操作。對於像是後台應用來說,用 <form> 表單來做整頁更新往往可以省下很多開發時間,甚至依賴瀏覽器的內建機制運作起來更加穩定。

FormData 在 JavaScript 中的應用

FormData 定義了一個介面方便開發者做像是 key/value 的應用,最常見的就是表單處理。一個 FormData 的宣告可以這樣子寫:

const formData = new FormData()
//                key     value
formData.append('name', 'Kalan');

如果將 form 的 element 放到 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 在 JavaScript 的應用

除此之外,如果將 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 的序列化也是由瀏覽器完成

用 fetch 與 FormData 傳送 multipart form-data 請求

總結

這兩篇文章解釋了 multipart/form-data 的應用與實際使用方法。第一篇文章解釋了 Content-Disposition 的含義, boundary 的用途,以及 multipart/form-data 的請求如何構造;第二篇文章說明了在實務上我們可以怎麼使用 form 與 FormData,並透過 JavaScript 來做 FormData 的處理與檔案上傳。

對於伺服器端來說,在網頁上做檔案上傳的動作也是一個 HTTP 請求,所以伺服器端必須要根據 Header 當中的資訊以及 multipart/form-data 定義的格式來解析資料,才有辦法正確拿到檔案內容,通常這些解析都已經被框架處理掉,但是在這邊要特別註明的是,在網頁上傳遞檔案沒有太神奇的魔法,背後仍然是奠基在 HTTP 請求之上。