在大型前端项目中,当有不少接口实现数据输入、流出并附加拦截,结合状态管理,抵御XSRF攻击等时,统一管理API接口就成为大型前端项目必须面对的环节。axios做为最流行的基于Promise的HTTP库能够同时运行在浏览器端和服务器端,已经成为大部分前端项目的首选。前端
经过JSON.stringify
咱们一样能够实现序列化,可是对于复杂ObjectJSON.stringify
的支持行不如qs.stringify
。因此经过引入qs这个库,qs能够帮咱们对深层嵌套的JSON以及Array形式进行序列化,让咱们的API封装兼容更多的场景。vue
var a = {name:'hehe',age:10}; qs.stringify(a) // 'name=hehe&age=10' JSON.stringify(a) // '{"name":"hehe","age":10}' 复制代码
例外:如今后台工程大多能够在body里面获取json,array等,某些状况下,可能后台是直接读取的字符串信息,这种状况下,qs.stringify封装参数中的JSON以及Array格式没法获取,须要使用JSON.stringify
去处理ios
在正式进行axios二次封装以前,简单了解一下axios对于配置项的处理;能够从axios暴露出来的方法了解,能够在axios.defaults上配置config,也能够在拦截器上以及新的instance config上去配置;经过阅读源码,发现其实axios的config配置是经过merge方法去实现的:nginx
axios.create = function create(instanceConfig) { return createInstance(mergeConfig(axios.defaults, instanceConfig)); }; 复制代码
axios default以及新的instance的config以外,也提供了初始化的默认config;vuex
var DEFAULT_CONTENT_TYPE = { 'Content-Type': 'application/x-www-form-urlencoded' }; function setContentTypeIfUnset(headers, value) { if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { headers['Content-Type'] = value; } } 复制代码
axios的config是这样一个逻辑:element-ui
1.默认的初始化config与defaultconfig进行mergejson
2.将第一步获得的结果和新instance上的config进行mergeaxios
经过分析,咱们能够直接将请求的接口进行配置化处理,更方便的一步化适应各类场景后端
axios给我提供了一个default系列的属性,能够直接向axios.default
的一些属性赋值,这个axios.default
的赋值会做用给所用axios请求;官方文档给我提供了一些参考:好比设置默认的baseURL,为基于token的请求把token放到header的Authorization中,以及设置post的请求类型;api
axios.defaults.baseURL = 'https://api.example.com'; axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; 复制代码
做为一个出色的http请求库,axios提供了强大请求拦截和响应拦截功能。
//引入vuex import store from '@/store' ... axios.interceptors.request.use(config => { //将token添加到了request的header里面 const token = store.state.token; config.headers.common['Authorization'] = token // loading return config }, error => { console.log(error) return Promise.reject(error) }) 复制代码
经过拦截器能够实现请求的前置操做,例如,这里实现了比较常见的将token添加到header中。固然,在default中处理token也是能够的。因此网上大部分对拦截器的操做都是能够放到defaults中执行的,并无什么区别;我的认为请求前拦截能够结合一些定时器已经前端监控相关插件的使用。
须要注意一下响应拦截的执行顺序,先执行axios.interceptors.response.use
而后再执行正常的响应处理;
// 响应拦截器 axios.interceptors.response.use( response => { // 这里的response返回的HTTP状态码为2XX的状况,能够在这里集中处理200+JSON形式中JSON中先后端约定的状态码 }, //这里的error返回的是HTTP状态码不是2XX的状况,能够在这里处理不一样HTTP的status error => { if (error.response.status) { switch (error.response.status) { case 401: //未登陆的处理 case 403: //权限不足的处理 break; case 404: // 404请求不存在的处理 break; // 其余错误,直接抛出错误提示 default: //默认处理 } return Promise.reject(error.response); } } }); 复制代码
经过上面对axios的具体config分析,咱们能够经过增长merge结合封装方法来实现多种场景的配置;能够实现诸如,是否跨域携带cookie,是否附带loading,配置特殊接口的请求时常等待等
能够建立一个config.js
const configMap = { defaultConfig: { withCredentials: false, baseURL: path.baseUrl, headers: { post: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, }, }, long: { timeout: 60000, }, nocookie: { withCredentials: false, }, ... 复制代码
经过在httpjs中引入
import configMap from './config' import { showFullScreenLoading, tryHideFullScreenLoading } from './loading' import merge from 'lodash.merge' ... merge(axios.defaults, configMap['defaultConfig']) function handleTypeString(type) { type.toLowerCase().split('-').map(item => merge(axios.defaults, configMap[item])) } export default { post(url, data, type) { handleTypeString(type) return axios({ method: 'post', 复制代码
这样能够实现多个接口请求的配置组合,后面的会覆盖前面的,一个接一个,实现axios请求的彻底配置化处理;
在vue中使用
import http from "@/api/http"; import path from "@/api/path"; //配合async await更加优雅 async test() { const res = await http.post(path.test, params, "long-nocookie"); 复制代码
有些人比较喜欢使用偏函数的方式再包装一层,也能够再增长封装一层使调用时候直接使用。
test(param) 复制代码
封装loading.js来处理部分url请求接口须要loading菊花图的状况;须要设置needLoadingCount来记录处理多个须要loading请求接口处理的状况。
import { Loading } from 'element-ui'; let loading; function startLoading() { // 使用Element loading.tart 方法 loading = Loading.service({ lock: true, text: 'loading……', background: 'rgba(0, 0, 0, 0.5)', }); } function endLoading() { // 使用Element loading.close 方法 loading.close(); } //经过needLoadingCount来记录,在多个地方使用loading时候处理 let needLoadingCount = 0; export function showLoading() { if (needLoadingCount === 0) { startLoading(); } needLoadingCount++;//eslint-disable-line } export function tryHideLoading() { if (needLoadingCount >= 0) needLoadingCount--;//eslint-disable-line if (needLoadingCount === 0) { endLoading(); } } 复制代码
在http中配置
if (type === 'long') { showFullScreenLoading() return axios(config).then(response=>{ tryHideFullScreenLoading() rerurn response; }) } //其余无loading的axios请求 return axios(config) 复制代码
在先后端分离的spa场景下,axios的baseUrl等各类环境参数是预先设定好的;而后打包成静态文件,上传到nginx或者tomcat相似的http服务器中,从本地开发到测试,提供静态文件给不一样的后台去使用,可能不一样后台设置的接口地址是不同的,为了不一个个的去打包,咱们须要配置一个针对不一样域名环境的封装;
import merge from 'lodash.merge' const path = { baseUrl: 'http://localhost:3000', login: '/users/login', test: '/test', }; const pathMap = { 'http://localhost:3001': { baseUrl: 'http://localhost:3001' }, 'http://localhost:3002': { baseUrl: 'http://localhost:3002',login:'/login' }, } const getClientIdByLocation = () => { const { href } = window.location; const matchedKey = Object.keys(pathMap).filter(url => href.indexOf(url) > -1); merge(path, pathMap[matchedKey]) }; getClientIdByLocation(); export default path; 复制代码
能够经过直接在pathMap中配置不一样的url对应的path对象,来实现处理不一样的url对应的baseUrl以及各类子路由状况不一样时候的状况