详解vue中Axios的封装与API接口的管理

此次封装分为初级版和进阶版两部分,初级版可做练手使用。实际上,axios封装与否皆可做项目,在组件内正常发起请求也是最原始的方式,但随着前端的发展越来越快,项目体量的增大,必然会更加需要模块化的思想,在这里把axios看做一个发起请求的模块即可...

准备工作

  1. 首先,假设你的目录结构如图:

每个人可能有不同的代码目录,这个不同强求完全一致,理解封装思想即可。

image.png

至于每个目录结构是做什么的,传送门从零使用vue-cli3+webpack4搭建项目

  1. axios简单介绍
    在vue项目中,和后台交互获取数据这块,我们通常使用的是axios库,它是基于promise的http库,可运行在浏览器端和node.js中。他有很多优秀的特性,例如拦截请求和响应、取消请求、转换json、客户端防御XSRF等。所以vue作者也是果断放弃了对其官方库vue-resource的维护,直接推荐我们使用axios库。如果还对axios不了解的,可以移步axios文档
  2. 安装axios
npm install axios 

初级版axios封装

有vue封装经验的小伙伴请跳过初级部分,直接查看进阶版即可。

  1. 在config中index.js中的内容:
    node中qs的知识点请参考官方文档
    qs.parse()将URL解析成对象的形式
    qs.stringify()将对象 序列化成URL的形式,以&进行拼接
import axios from "axios"; 
import qs from "qs";

// 延时时间
const http = axios.create({
  //baseURL:"XXXX",
  timeout: 5000
})


//请求拦截
http.interceptors.request.use((config) => {
  // post请求对请求数据进行序列化
  if (config.method === "post") {
    config.data = qs.stringify(config.data);
  }
  return config;

}, (err) => {
  return Promise.reject(err);
})



//响应拦截
http.interceptors.response.use((res) => {
  return res.data;
}, (err) => {
  return Promise.reject(err);
})


export default (method, url, data = null) => {
  if (method == "post") {
    return http.post(url, data);
  } else if (method == "get") {
    return http.get(url, {
      params: data
    });
  } else {
    return;
  }
}

  1. 在api文件夹login.js中对请求的管控,如果项目庞大,分文件管控请求更方便,login.js统一管控登录接口:
// 引入
import http from "../config/index.js"

const RegCode = (arg) => http("post","/node/user/getCode", arg)
const Register=(arg)=>http("post","/node/user/register",arg)
const LoginBtn=(arg)=>http("post","/node/user/login",arg)
let Login={
  RegCode,
  Register,
  LoginBtn
}
// 导出
export default Login

  1. 在vuex中login.js的使用方式:
import Login from "../../api/login"
const state = {
  loginState: false
}
// 在actions中做异步请求,通过async、await可是异步请求同步执行
const actions = {
  async actionLogin({
    commit
  }, params) {
    let loginData = await Login.LoginBtn(params)
    commit("mutateLogin", loginData)
  }
}

const mutations = {
  mutateLogin(state, params) {
    if (params.state = 1) {
      state.loginState = true
      alert('login success')
    } else {
      alert('login fail')
    }
  }
}

export default {
  state,
  mutations,
  actions,
  namespaced: true
}

  1. 在其他需使用的组件里直接调用即可
/* 新建了一个非常简易的模板,便于理解 */
<template>
  <div id='app'>
    <div @click="handlerLogin">登录</div>
  </div>
</template>
<script>
//导入组件
import Vuex from "vuex"
export default {
  name: 'App',
  methods: {
    ...Vuex.mapActions({
      handlerLogin: "login/actionLogin"
    })
  }
}
</script>
<style>
/* 样式代码 */
#app {
}
</style>

以上,初级版的axios封装已完成。

进阶版axios封装

此部分会更详细一些,希望大家多提建议。

  1. 引入(config文件夹下的index.js中)
// 在http.js中引入axios
import axios from 'axios'
// 引入qs模块,用来序列化post类型的数据
import QS from 'qs'
// mint-ui的toast提示框组件,大家可根据自己的ui组件更改
import { Toast } from 'mint-ui';

项目中如何使用mint-ui,请参考官方文档

  1. 环境的切换
    我们的项目环境可能有开发环境、测试环境和生产环境。我们通过node的环境变量来匹配我们的默认的接口url前缀。axios.defaults.baseURL可以设置axios的默认请求地址就不多说了。
// 环境的切换
if (process.env.NODE_ENV === 'development') { 
 axios.defaults.baseURL = 'https://www.development.com';} 
else if (process.env.NODE_ENV === 'test') { 
 axios.defaults.baseURL = 'https://www.test.com'
} 
else if (process.env.NODE_ENV === 'production') { 
 axios.defaults.baseURL = 'https://www.production.com'
}
  1. 设置请求超时
    通过axios.defaults.timeout设置默认的请求超时时间。例如超过了10s,就会告知用户当前请求超时,请刷新等。
axios.defaults.timeout = 10000;
  1. post请求头的设置
    post请求的时候,我们需要加上一个请求头,所以可以在这里进行一个默认的设置,即设置post的请求头为application/x-www-form-urlencoded;charset=UTF-8
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
  1. 请求拦截
    我们在发送请求前可以进行一个请求的拦截,为什么要拦截呢,我们拦截请求是用来做什么的呢?比如,有些请求是需要用户登录之后才能访问的,或者post请求的时候,我们需要序列化我们提交的数据。这时候,我们可以在请求被发送之前进行一个拦截,从而进行我们想要的操作。
// 先导入vuex,因为我们要使用到里面的状态对象
// vuex的路径根据自己的路径去写,token尽量写成本地存储的形式,不然的话vuex刷新之后就没有了
import store from '@/store/index';
 
// 请求拦截器
axios.interceptors.request.use( 
 config => { 
 // 每次发送请求之前判断vuex中是否存在token 
 // 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况
 // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断 
 const token = store.state.token
 token && (config.headers.Authorization = token)
 return config
 }, 
 error => { 
 return Promise.error(error)
})

这里说一下token,一般是在登录完成之后,将用户的token通过localStorage或者cookie存在本地,然后用户每次在进入页面的时候(即在main.js中),会首先从本地存储中读取token,如果token存在说明用户已经登陆过,则更新vuex中的token状态。然后,在每次请求接口的时候,都会在请求的header中携带token,后台人员就可以根据你携带的token来判断你的登录是否过期,如果没有携带,则说明没有登录过。这时候或许有些小伙伴会有疑问了,就是每个请求都携带token,那么要是一个页面不需要用户登录就可以访问的怎么办呢?其实,你前端的请求可以携带token,但是后台可以选择不接收啊!

  1. 响应拦截
// 响应拦截器
axios.interceptors.response.use( 
 response => { 
 // 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据 
 // 否则的话抛出错误
 if (response.status === 200) {  
  return Promise.resolve(response); 
 } else {  
  return Promise.reject(response); 
 } 
 }, 
 // 服务器状态码不是2开头的的情况
 // 这里可以跟你们的后台开发人员协商好统一的错误状态码 
 // 然后根据返回的状态码进行一些操作,例如登录过期提示,错误提示等等
 // 下面列举几个常见的操作,其他需求可自行扩展
 error => {  
 if (error.response.status) {  
  switch (error.response.status) {  
  // 401: 未登录
  // 未登录则跳转登录页面,并携带当前页面的路径
  // 在登录成功后返回当前页面,这一步需要在登录页操作。  
  case 401:   
   router.replace({   
   path: '/login',   
   query: { 
    redirect: router.currentRoute.fullPath 
   }
   });
   break;
 
  // 403 token过期
  // 登录过期对用户进行提示
  // 清除本地token和清空vuex中token对象
  // 跳转登录页面  
  case 403:
   Toast('登录过期,请重新登录');
   // 清除token
   localStorage.removeItem('token');
   store.commit('loginSuccess', null);
   // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面 
   setTimeout(() => {   
   router.replace({    
    path: '/login',    
    query: { 
    redirect: router.currentRoute.fullPath 
    }   
   });   
   }, 1000);   
   break;
 
  // 404请求不存在
  case 404:
   Toast('网络请求不存在');
   break;
  // 其他错误,直接抛出错误提示
  default:
   Toast(error.response.data.message);
  }
  return Promise.reject(error.response);
 }
 } 
});

要注意的是,上面的Toast()方法,是我引入的mint-ui库中的toast轻提示组件,你可根据你的ui库,对应使用你的一个提示组件。

  1. 封装get方法和post方法
    我们常用的ajax请求方法有get、post、put等方法,相信小伙伴都不会陌生。axios对应的也有很多类似的方法,不清楚的可以看下文档。但是为了简化我们的代码,我们还是要对其进行一个简单的封装。下面我们主要封装两个方法:get和post。
    get方法:我们通过定义一个get函数,get函数有两个参数,第一个参数表示我们要请求的url地址,第二个参数是我们要携带的请求参数。get函数返回一个promise对象,当axios其请求成功时resolve服务器返回 值,请求失败时reject错误值。最后通过export抛出get函数。
/**
 * get方法,对应get请求
 * @param {String} url [请求的url地址]
 * @param {Object} params [请求时携带的参数]
 */
export function get(url, params){ 
 return new Promise((resolve, reject) =>{ 
 axios.get(url, {  
  params: params 
 }).then(res => {
  resolve(res.data);
 }).catch(err =>{
  reject(err.data) 
 }) 
});}

post方法:原理同get基本一样,但是要注意的是,post方法必须要使用对提交从参数对象进行序列化的操作,所以这里我们通过node的qs模块来序列化我们的参数。这个很重要,如果没有序列化操作,后台是拿不到你提交的数据的。这就是文章开头我们import QS from 'qs';的原因。

/** 
 * post方法,对应post请求 
 * @param {String} url [请求的url地址] 
 * @param {Object} params [请求时携带的参数] 
 */
export function post(url, params) {
 return new Promise((resolve, reject) => {
  axios.post(url, QS.stringify(params))
 .then(res => {
  resolve(res.data);
 })
 .catch(err =>{
  reject(err.data)
 })
 });
}

这里有个小细节说下,axios.get()方法和axios.post()在提交数据时参数的书写方式还是有区别的。区别就是,get的第二个参数是一个{},然后这个对象的params属性值是一个参数对象的。而post的第二个参数就是一个参数对象。两者略微的区别要留意哦!

  1. api统一管理
    上面说了,我们会在api文件夹统一管控请求接口。
    首先我们在login.js中引入我们封装的get和post方法。
/** 
 * api接口统一管理
 */
import { get, post } from './http'

现在,例如我们有这样一个接口,是一个post请求:

http://www.baidu.com/api/login

在login.js中这样封装

export const login= p => post('api/login', p);

我们定义了一个login方法,这个方法有一个参数p,p是我们请求接口时携带的参数对象。而后调用了我们封装的post方法,post方法的第一个参数是我们的接口地址,第二个参数是login的p参数,即请求接口时携带的参数对象。最后通过export导出login。
然后在我们的页面中可以这样调用我们的api接口:(如果想使用初级版里面的调用方式是一样的,初级版不过是将异步转为同步了)

import { login } from '../../api/login.js';// 导入我们的api接口
export default {  
 name: 'Address', 
 created () {
  this.onLoad();
 },
 methods: {   
  // 获取数据   
  onLoad() {
   // 调用api接口,并且提供了两个参数    
   login({     
    username:xxx,
    password:xxx
   }).then(res => {
    // 获取数据成功后的其他操作
    ………………    
   })   
  }  
 }
}

其他的api接口,像login.js一样扩展即可。友情提示,为每个接口写好注释哦!!!api接口管理的一个好处就是,我们把api统一集中起来,如果后期需要修改接口,我们就直接在api中找到对应的修改就好了,而不用去每一个页面查找我们的接口然后再修改会很麻烦。关键是,万一修改的量比较大,就呵呵哒了。还有就是如果直接在我们的业务代码修改接口,一不小心还容易动到我们的业务代码造成不必要的麻烦。

好了,最后把完成的axios封装代码奉上。

/**axios封装
 * 请求拦截、相应拦截、错误统一处理
 */
import axios from 'axios';
import QS from 'qs';
import { Toast } from 'mint-ui';
import store from '../store/index'
 
// 环境的切换
if (process.env.NODE_ENV === 'development') { 
 axios.defaults.baseURL = 'http://www.dev.com';
} else if (process.env.NODE_ENV === 'test') { 
 axios.defaults.baseURL = 'http://www.test.com';
} else if (process.env.NODE_ENV === 'production') { 
 axios.defaults.baseURL = 'http://www.pro.com';
}
 
// 请求超时时间
axios.defaults.timeout = 10000;
 
// post请求头
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
 
// 请求拦截器
axios.interceptors.request.use( 
 config => {
  // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
  // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
  const token = store.state.token;  
  token && (config.headers.Authorization = token);  
  return config; 
 }, 
 error => {  
  return Promise.error(error); 
 })
 
// 响应拦截器
axios.interceptors.response.use( 
 response => {  
  if (response.status === 200) {   
   return Promise.resolve(response);  
  } else {   
   return Promise.reject(response);  
  } 
 },
 // 服务器状态码不是200的情况 
 error => {  
  if (error.response.status) {   
   switch (error.response.status) {    
    // 401: 未登录    
    // 未登录则跳转登录页面,并携带当前页面的路径    
    // 在登录成功后返回当前页面,这一步需要在登录页操作。    
    case 401:     
     router.replace({      
      path: '/login',      
      query: { redirect: router.currentRoute.fullPath } 
     });
     break;
    // 403 token过期    
    // 登录过期对用户进行提示    
    // 清除本地token和清空vuex中token对象    
    // 跳转登录页面    
    case 403:      
     Toast('登录过期,请重新登录');     
     // 清除token     
     localStorage.removeItem('token');     
     store.commit('loginSuccess', null);     // 不太懂的话可不对状态码进行操作
     // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
     setTimeout(() => {      
      router.replace({       
       path: '/login',       
       query: { 
        redirect: router.currentRoute.fullPath 
       }      
      });     
     }, 1000);     
     break; 
    // 404请求不存在    
    case 404:     
     Toast('网络请求不存在');     
    break;    
    // 其他错误,直接抛出错误提示    
    default:     
     Toast(error.response.data.message);   
   }   
   return Promise.reject(error.response);  
  }  
 }
);
/** 
 * get方法,对应get请求 
 * @param {String} url [请求的url地址] 
 * @param {Object} params [请求时携带的参数] 
 */
export function get(url, params){ 
 return new Promise((resolve, reject) =>{  
  axios.get(url, {   
   params: params  
  })  
  .then(res => {   
   resolve(res.data);  
  })  
  .catch(err => {   
   reject(err.data)  
  }) 
 });
}
/** 
 * post方法,对应post请求 
 * @param {String} url [请求的url地址] 
 * @param {Object} params [请求时携带的参数] 
 */
export function post(url, params) { 
 return new Promise((resolve, reject) => {   
  axios.post(url, QS.stringify(params))  
  .then(res => {   
   resolve(res.data);  
  })  
  .catch(err => {   
   reject(err.data)  
  }) 
 });
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流。

你的赞是我前进的动力

求赞,求评论,求分享...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容