很多时候,在 post 提交数据时我们常采用 application/json、application/x-www-form-urlencoded 等类型,也确实能够覆盖到大部分的场景,但是有一些场景下,比如文件上传的时候,就不算是好的解决方案了,application/json 作为请求头 Content-Type 字段值时,表示告知服务端参数是序列化后的 JSON 字符串,所以一般在传参时都会用 JSON.stringify 序列化一下,且浏览器对 JSON.stringify API 支持程度比较高。
但是 JSON.stringify 在转换某一些数据结构时会出问题,比如 会丢失 function 类型的参数、循环引用时会报错、Blob /File 对象会被转化成 {} 等等,,可以参考 为何不推荐使用 JSON.stringify 做深拷贝,不过 JSON.stringify 还有第三个参数,有兴趣的同学可以去了解下,这是其一,其二,有同学要说了,如果要是图片那可以转换成 base64 格式进行上传解决,这种方式虽然可行,但是转换成 base64 格式需要很多字符,占用很多资源,而且很长,不便于阅读,另外就是服务端接收到这个参数还得解析,很麻烦,此时,FormData 就可用上了。
const specialFileType = ['Blob', 'File']; // 堆代码 duidaima.com function formatData (_data) { const data = new window.FormData() for (const key in _data) { let value = _data[key] if (_data[key] instanceof Object && !specialFileType.includes(_data[key].constructor.name)) { value = JSON.stringify(_data[key]) } data.append(key, value) } return data }
cosnt View = () => { const [fileA, setFileA] = useState(null); const [fileB, setFileB] = useState(null); const handleClick = () => { console.log('fileA:', fileA) console.log('fileB:', fileB) const p = { a: { a1: 11, a2: 22 }, b: [1,2,3], c: 123, d: fileA[0], e: fileB[0], } const data = formatData(p); axios({ method: 'POST', url: '/aa', data, // headers: { // 'content-type': 'multipart/formdata' // }, }) } return <div> <div onClick={handleClick}>发送请求</div> <input type='file' onChange={(a) => { const v = a.target.files; setFileA(v); }} /> <input type='file' onChange={(a) => { const v = a.target.files; setFileB(v); }} /> </div> }
可以看到 每一个参数之间都有一个 ------WebKitFormBoundary *** 区分开,这实际上是 FormData 的规范标志,后面的字符串是浏览器帮我们自动创建的,以 ------WebKitFormBoundary *** 作为分隔符,也作为开始和结尾,其内容主要有 Content-Disposition、Content-Type 等,其中 Content-Disposition 是必选项, name 属性代表着表单元素的 key,filename 则是上传文件的名称,也可以使用 FormData 第三个参数更改 。
另外,我在发送请求时,并没有更改请求头里面的 Content-Type,但实际上我们看到的是正确的 multipart/form-data,这是因为现在的浏览器比较智能,当客户端未设置请求头的 Content-Type 时,请求参数为对象时,某一些浏览器会自动帮我们在 请求头中添加 Content-Type: text/plain,如果传输的数据是 FormData,也会自动帮我们加上 Content-Type: multipart/form-data 等,可能不同浏览器表现行为不一样,但是最好的方式就是客户端与服务端约定好 Content-Type 类型,固定传递。