上一篇文章中,我們介紹了 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 動態更新資料的做法。
雖然動態更新的方式的確改善了使用者體驗,但是要實現一個好的表單設計卻需要考慮許多細節:
- 錯誤處理
- 狀態轉換
- 資料保存
- 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 放到 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 的序列化也是由瀏覽器完成
總結
這兩篇文章解釋了 multipart/form-data
的應用與實際使用方法。第一篇文章解釋了 Content-Disposition
的含義, boundary 的用途,以及 multipart/form-data 的請求如何構造;第二篇文章說明了在實務上我們可以怎麼使用 form 與 FormData,並透過 JavaScript 來做 FormData 的處理與檔案上傳。
對於伺服器端來說,在網頁上做檔案上傳的動作也是一個 HTTP 請求,所以伺服器端必須要根據 Header 當中的資訊以及 multipart/form-data
定義的格式來解析資料,才有辦法正確拿到檔案內容,通常這些解析都已經被框架處理掉,但是在這邊要特別註明的是,在網頁上傳遞檔案沒有太神奇的魔法,背後仍然是奠基在 HTTP 請求之上。