我与axios的那些事

501 阅读7分钟

前言:

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],
    },
});

image.png ☝☝☝ 我们可以看到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 控制台显示 image.png

看到表单上传编码格式为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控制台 image.png 在我们没有设置 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控制台 image.png

  • 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);
}
  • 细心的我们还会发现一个小特别,在控制台中发现了两条请求记录

image.png

这里我们直接给出答案,多出来的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,不需要设置这个请求头,dataFormData情况下,axios也会删除这个请求头,让浏览器会自动设置
  • (axios默认转换这种)application/jsondata为一个对象的时候,axios会自动设置请求头。

End


如果本文对你有一点点帮助,点个赞支持一下吧,你的每一个【赞】都是我创作的最大动力,感谢支持 ^_^

参考文献:
用 HTTP 提交数据,基本就这 5 种方式
RESTful API 设计指南
XMLhttpRequest
axios源码解析 强烈推荐大家去看
四种常见的 POST 提交数据方式
预检请求 OPTIONS