前后端分离模式下接口的请求方式主要以下两种
- fetch API
- axios库
首先来回顾一下基础知识
post请求content-type
HTTP协议规定post请求的数据必须放在请求体内,content-type规定请求体数据的编码格式,服务端需要正确解析不同编码格式的请求体数据。Content-Type 用于规定客户端通过http或https协议向服务器发起请求时,传递的请求体中数据的编码格式。因为get请求是直接将请求数据以键值对通过&号连接(key1=value1&key2=value2)的方式附加到url地址后面,不在请求体中,所以get请求中不需要设置Content-Type。
application/x-www-form-urlencoded(默认格式)
- 数据以
key=value&key=value
的形式传递 - key和value中的特殊字符会进行编码,如空格被编译为
20%

application/json
- 上一种方式不能满足传递比较复杂的数据结构,如对象数组多层嵌套。直接传递json对象时使用。Angular中的Ajax,默认就是提交 JSON 字符串

multipart/form-data(表单上传文件,暂不深入)
text/xml
序列化
- 序列化是将对象的状态信息转换为可以存储或传输的形式的过程,例如
{a:1, b:2}
转换成a=1&b=2
。 - 当content-type为application/x-www-form-urlencoded 格式进行post请求传递数据时,需要使用qs.stringify来转换data的格式。
- 注意在传递数组,对象这种形式时
arr = [1,2,3]
会被转为a[0]=1&a[1]=2&a[2]=3
,obj= { a: 1, b: 2 }
会被转为obj[a]=1&obj[b]=2
这种形式,所以传递复杂数据类型时最好使用application/json - 常用qs库
qs.stringify(obj)
序列化qs.parse(string)
反序列化
拦截器
设置全局loading
import axios from 'axios'
import { Toast } from 'vant'
const instance = axios.create()
instance.defaults.headers.post['Content-type'] = 'application/x-www-form-urlencoded'
let pendingRequestCount = 0 // 计数器,多个请求时进行loading合并
function showLoading () {
if (pendingRequestCount === 0) {
Toast.loading({
message: '加载中...',
forbidClick: true
})
}
pendingRequestCount++
}
function hideLoading () {
if (pendingRequestCount === 0) {
Toast.clear()
}
pendingRequestCount-- // 注意不是if...else...关系 调用即进行数目更新
}
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
showLoading()
return config
}, function (error) {
return Promise.reject(error)
})
// 添加响应拦截器
instance.interceptors.response.use(
res => {
hideLoading()
return res
},
error => {
return Promise.reject(error)
})
export default instance
结合cancelToken拦截重复请求
// 每次请求都会记录在此对象里,用于判断是否重复
const pending = {}
// axios.CancelToken
const { CancelToken } = axios
const paramsList = ['get', 'delete']
const dataList = ['post', 'put', 'patch']
// 区分参数位置
const isTypeList = method => {
if (paramsList.includes(method)) {
return 'params'
} else if (dataList.includes(method)) {
return 'data'
}
}
/**
* 获取请求唯一值(key)
* @param {Object} config - axios拦截器的config
* @param {Boolean} isResult - 截取url唯一,这里区别请求前和请求后,因为前者和后者的url不同,所以需要区分一下
*/
function getRequestIdentify (config, isResult = false) {
const url = isResult
? config.baseURL + config.url.substring(1, config.url.length)
: config.url
const params = { ...(config[isTypeList(config.method)] || {}) }
delete params.t
return encodeURIComponent(url + JSON.stringify(params))
}
/**
* 每次请求前 清除上一个跟它相同的还在请求中的接口
* @param {String} key - url唯一值
* @param {Boolean} isRequest - 是否执行取消重复请求
*/
function removePending (key, isRequest = false) {
if (pending[key] && isRequest) {
console.log(pending[key])
pending[key]('取消重复请求')
}
delete pending[key]
}
// 请求前
instance.interceptors.request.use(
config => {
// 获取该次请求的唯一值
const requestData = getRequestIdentify(config, true)
console.log(requestData)
// debugger
// 删除上一个相同的请求
removePending(requestData, true)
// 实例化取消请求,并同时注入pending
config.cancelToken = new CancelToken(c => {
pending[requestData] = c
})
return config
},
error => {
Promise.reject(error)
}
)
// 请求完成后
instance.interceptors.response.use(
response => {
// 把已经完成的请求从 pending 中移除
const requestData = getRequestIdentify(response.config)
removePending(requestData)
return response
},
error => {
return Promise.reject(error)
}
)
我的使用
- 业务场景(每个网络请求都新增一个参数project_id)
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
if (config.method === 'post' && config.data.indexOf('project_id') === 0) {
config.data = config.data + `&project_id=${getQueryString('project_id')}`
} else if (config.method === 'get' && (!config.params || !config.params.project_id)) {
config.params = {
...config.params || {},
project_id: getQueryString('project_id')
}
}
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 发送请求前处理request的数据
instance.defaults.transformRequest = [function (data) {
return data + `&project_id=${getQueryString('project_id')}`
}]
transformRequest和interceptors的区别?
- 两者的执行顺序不同,先 request interceptors再transformRequest
- transformRequest主要对数据data进行处理
- transformRequest是同步的,拦截器可异步
我在transformRequest中做了什么:post数据序列化
const instance = axios.create({
transformRequest: [function (data) {
data = qs.stringify(data) // 进行post请求数据的序列化
return data
}]
}
- 其实在拦截器中做也可以
if (config.method === 'post') {
config.data = qs.stringify({
...config.data
})
}
- 不进行qs.stringfy当直接传递JS对象默认会被转换为application/json会发送json格式,qs.stringfy让axios发送表单请求形式的键值对post数据
我在响应拦截器里做了什么:res的处理
- 起因:res.data.data,res.data.error,res.data.msg繁琐的写法,层次深,更好的做法是在业务代码中这样使用res的数据:res.data,res.error,res.msg,添加响应拦截器或transformResponse
instance.interceptors.response.use(
res => {
// 对响应数据做点什么
if (res.data && res.error + '' && handleError[res.error]) {
handleError[res.error](res)
}
return res.data
},
error => {
// 对响应错误做点什么
return Promise.reject(error)
})
设计请求层(axios封装和api管理)
封装有两种方法:一种是创建一个axios实例,另外一种是直接修改axios的defaults
api
├── config.js // baseUrl,Authorization,Content-Type拦截器
├── index.js // 引入配置文件和各个功能模块
├── home.js // api接口模块化
├── list.js
├── detail.js
└── order.js
环境的切换(online,pre,dev) | proxy切换
import api from '@/api' // 导入api接口
Vue.prototype.$api = api; // 将api挂载到vue的原型上直接在业务代码中调用`this.$api.xxx`而不是`import { xxx } from '@/api'`