前言:
axios作为一个老牌的HTTP库,相信大家都用过啦。 作为一个前端小白,在与后端对接中,总会遇到些小小疑惑。
- params 和 data 属性有什么差别
- GET 设置content-type,要设置 data?
- qs.stringify 什么时候用,什么时候不用
- headers的设置
application/x-www-form-urlencoded; application/json multipart/form-data - 上传文件怎么玩
- 下载文件怎么玩
- ......
💡 接下来让我们梳理并且解决一下这些疑惑,同时从向服务端提交数据的几种方式来展开😀
URL Param
Restful 的规范允许把参数写在 url 中,从服务端中获取参数
// GET /zoos/ID:获取某个指定动物园的信息
// 原生ajax
const xhr = new XMLHttpRequest()
xhr.open('GET','.../zoos/1')
xhr.send()
// axios中只要简单的拼起来就好啦
Axios({
url:'.../zoos/1',// 获取id为1的动物园的信息
method:'GET'
}).then(res=>{console.log('返回结果在这里',res)})
Query
请求地址url 中
?后面的用&分隔的字符串传递数据,注意:遇到中文或者特殊字符时,需要进行转换
// 原生ajax
const xhr = new XMLHttpRequest()
xhr.open('GET','.../zoos?id=1',true) // 哈哈,这里最后一个参数是表示异步
xhr.send()
// axios
Axios({
url:'.../zoos?id=1',// 获取id为1的动物园的信息
method:'GET'
}).then(res=>{console.log('返回结果在这里',res)})
// 推荐方式:使用params参数,易读
Axios({
url:'.../zoos',
method:'GET',
params:{
id:1
}
}).then(res=>{console.log('返回结果在这里',res)})
- Axios 会对params参数进行处理(中文,特殊字符,等)
axios({
url: ".../zoos",
method: "GET",
params: {
id:1,
test: "我是中文,我是特殊字符,$+[。",
list: [1, 2, 3],
},
});
☝☝☝ 我们可以看到axios对中文特殊字符,以及数组都进行了特殊处理,来看下axios内部是如何处理的
// 转义参数
function encode(val) {
return encodeURIComponent(val)
.replace(/%3A/gi, ":")
.replace(/%24/g, "$")
.replace(/%2C/gi, ",")
.replace(/%20/g, "+")
.replace(/%5B/gi, "[")
.replace(/%5D/gi, "]");
}
// 循环处理参数
utils.forEach(params, function serialize(val, key) {
if (val === null || typeof val === "undefined") {
return;
}
/* 数组key值处理,知道为啥list[]=1这种出现了吧*/
if (utils.isArray(val)) {
key = key + "[]";
} else {
val = [val];
}
utils.forEach(val, function parseValue(v) {
if (utils.isDate(v)) {
v = v.toISOString();
} else if (utils.isObject(v)) {
v = JSON.stringify(v);
}
parts.push(encode(key) + "=" + encode(v));
});
});
- 对axios的处理不满意?有办法:
paramsSerializer函数,自定义处理params的参数
// 使用方式:
paramsSerializer: function (params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
这里扩展一下 Qs 的arrayFormat几种配置方式的知识
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })
// 'a[0]=b&a[1]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })
// 'a[]=b&a[]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })
// 'a=b&a=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'comma' }) // 标重点,大部分用这个,哈哈哈哈
// 'a=b,c'
- GET方式设置请求头
在axios中,GET方式设置
Content-type请求头,必须必须设置一个data的属性,为什么要设置data呢,看源码片段吧(😑别问为什么GET方式需要设置请求头,总有些玄学接口存在)
// Axios 源码片段
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
// requestData 就是 data的发起请求的时候的data属性
// requestData 没有声明的时候,删除content-type属性
if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
// Remove Content-Type if data is undefined
delete requestHeaders[key];
} else {
// Otherwise add header to the request
request.setRequestHeader(key, val);
}
});
}
// 所以 Axios GET方式使用请求头正确姿势
Axios({
url:'.../zoos',
method:'GET',
headers: {
'Content-Type': 'application/text'
},
params:{
id:1
},
data:{}// 哈哈哈,略显滑稽
}).then(res=>{console.log('返回结果在这里',res)})
关于通过URL传递参数的方式就讲到这里啦,下面的方式通过body传递参数
application/x-www-form-urlencoded
先来看下我们传统的表单form提交,没有设置 enctype 参数的情况下,默认就是application/x-www-form-urlencoded
<form method="post" action=".../user/save.action">
<input type="hidden" name="name" value="我是名字" />
<input type="hidden" name="phone" value="123456789" />
<input type="hidden" name="address" value="我是地址" />
<input type="submit" value="提交" />
</form>
chrome 控制台显示
看到表单上传编码格式为application/x-www-form-urlencoded(Request Headers中),参数的格式为key=value&key=value,这不就是Query方式传递参数的方式,GET放到了url中,POST放到了主体body中嘛
// 原生
const xhr = new XMLHttpRequest();
xhr.open("POST", ".../user/save.action");
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
// "name=我是名字&phone=123456789&phone=我是地址"
xhr.send(`name=${encodeURIComponent('我是名字')}&phone=123456789&phone=${encodeURIComponent('我是地址')}`);
// axios
axios({
url:".../user/save.action",
headers:{
"Content-Type":"application/x-www-form-urlencoded"
},
// data必须是这种表单数据格式的字符串;服务端才能解析出来
data: `name=${encodeURIComponent('我是名字')}&phone=123456789&address=${encodeURIComponent('我是地址')}`
// 这种字符串的格式,拼接麻烦,使用qs.stringify快速生成
//data: qs.stringify({name:'我是名字',phone:123456789,address:'我是地址'})
})
axios.data 属性存放 请求主体
qs.stringif()用在"Content-Type"配置为"application/x-www-form-urlencoded",服务端需要这种格式的数据
multipart/form-data
MDN原话:
application/``x-www-form-urlencoded: 数据被编码成以'&'分隔的键-值对, 同时以'='分隔键和值. 非字母或数字的字符会被 percent-encoding: 这也就是为什么这种类型不支持二进制数据(应使用multipart/form-data代替)
通俗就是涉及到文件传输,应该用multipart/form-data方式
<input type="file" name="file" id="file" />
<button id="btn">提交</button>
<script>
document.querySelector("#btn").onclick = upload;
function upload() {
const xhr = new XMLHttpRequest();
xhr.open("post", "http://localhost:3000/app/upload");
// *** 为什么要注释这一行,往下看,当然自己尝试一下也可以 ***
// xhr.setRequestHeader("Content-type", "multipart/form-data");
var formData = new FormData();
formData.append("name", "起个名字吧");
const file = document.querySelector("#file").files[0];
formData.append("file", file);
xhr.send(formData);
}
</script>
看下chrome控制台
在我们没有设置
multipart/form-data 的情况下,浏览器自动配置了 Content-Type 的请求头,并且我们注意到 在 Content-Type 的后面有一个 boundary=----WebKitFormBoundaryxuqnB3aWRq2HyCbP。
boundary作用:在请求体中我们以boundary这个又长又复杂的内容(避免和正文冲突)进行数据的分割。- 注意:一旦我们设置了
xhr.setRequestHeader("Content-type", "multipart/form-data");,就会把含有boundary的覆盖掉,导致服务端没有进行数据的处理
Axios中的处理:
const formData = new FormData();
formData.append("name", "起个名字吧");
const file = document.querySelector("#file").files[0];
formData.append("file", file);
axios({
url: "http://localhost:3000/app/upload",
method: "POST",
// 这一行可无
headers: {
"Content-type": "multipart/form-data;",
},
data: formData,
});
// axios源码中有这样一个片段
// 请求体一旦为formData的格式,axios会删除Content-type,请求头会让浏览器自己设置
if (utils.isFormData(requestData)) {
delete requestHeaders["Content-Type"]; // Let the browser set it
}
Tips:
multipart/form-data用了boundary(又长又复杂),所以 请求体 会比 使用application/x-www-form-urlencoded方式大一些
上面提到的 multipart/form-data,和 application/x-www-form-urlencoded,这两种方式都是原生form表单中enctype 属性指定的,当然还有一种text/plain,这种比较少,随着前端的发展,使用 Ajax 进行数据交互之,也出现了其他的提交数据的方式。
application/json
Content-Type: application/json : 告诉服务器,hi,我这次的请求体是序列化的JSON字符串。
好处:
- 支持比键值对复杂得多的结构化数据,
JSON的体积更小.前端处理起来也更简单,不需要encodeURIComponent处理数据,也不用去塞进formData中 - 服务端语言也都有处理
JSON的函数,使用起来也比较方便。
// 原生xhr
const xhr = new XMLHttpRequest();
xhr.open("POST", "http://127.0.0.1:3000/app/save");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(
JSON.stringify({
title: "123",
list: [{ name: "小王" }, { name: "小红" }],
})
);
// axios
axios({
url: "http://127.0.0.1:3000/app/save",
method: "POST",
data: {
title: "123",
list: [{ name: "小王" }, { name: "小红" }],
},
});
chrome控制台
- axios中的data为什么不需要
JSON.stringify()也不需要设置请求头,因为在axios 内部已经帮忙处理过了,只需要传一个对象就可以帮忙处理了
// axios源码片段,是一个对象或者设置了application/json的请求头
if (
utils.isObject(data) ||
(headers && headers["Content-Type"] === "application/json")
) {
setContentTypeIfUnset(headers, "application/json");
return stringifySafely(data);
}
- 细心的我们还会发现一个小特别,在控制台中发现了两条请求记录
这里我们直接给出答案,多出来的Options请求,叫预检请求。
- 检测服务器所支持的请求方法
- CORS 中的预检请求 是浏览器自身发起的请求,安全安全的,不是bug哈
Axios 下载文件
async function exportFile() {
const { data, headers } = await axios({
url: "http://localhost:7001/api/exportFile",
method: "POST",
responseType: "blob", // 表示服务器响应的数据类型
});
const blob = new Blob([data]);
// 后端会将名字放在 Content-Disposition 头中
const fileName = headers["content-disposition"].split("=")[1];
// 通过a标签将文件下载
const link = document.createElement("a");
link.download = fileName;
link.style.display = "none";
link.href = URL.createObjectURL(blob);
document.body.append(link);
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
}
这里就是简单的例子,实际情况会稍微复杂一点,传参的时候用multipart/form-data,还是application/x-www-form-urlencoded,或者是application/json的方式,都要和后端小伙伴好好配合。
总结
params参数 拼接到url后面,data是放请求主体的位置GET需要设置Content-Type,传一个data就好(特殊情况才会出现)- (默认form表单这种)
application/x-www-form-urlencoded方式,需要我们手动需要对data进行qs.stringify转换 - (上传文件用这种)
multipart/form-data,不需要设置这个请求头,data为FormData情况下,axios也会删除这个请求头,让浏览器会自动设置 - (axios默认转换这种)
application/json,data为一个对象的时候,axios会自动设置请求头。
End
如果本文对你有一点点帮助,点个赞支持一下吧,你的每一个【赞】都是我创作的最大动力,感谢支持 ^_^
参考文献:
用 HTTP 提交数据,基本就这 5 种方式
RESTful API 设计指南
XMLhttpRequest
axios源码解析 强烈推荐大家去看
四种常见的 POST 提交数据方式
预检请求 OPTIONS