上篇文章,我们分享了"前端开发:一起了解下HTML表单(form)中form-data的玩法"。今天继续以 <form>
这个标签为主题,探讨浏览器如何处理这个HTML 标签,以及身为开发者的我们应该注意哪些事情。
具体来说,这篇文章会包含下列几个有关于表单应用的部分:
-
<form/>
标签背后做了哪些事 -
FormData 在JavaScript 当中的应用
-
FormData 与
fetch
的搭配 -
JavaScript 如何操作档案上传
再谈form 标签 {#再談-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-在-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>
打开console,发现如下显示:
除此之外,如果将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 请求之上。