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

1,416 阅读14分钟

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

准备工作

目录结构初始化

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

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

image.png

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

axios简单介绍

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

安装axios

npm install axios 

初级版axios封装

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

在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;
  }
}

API统一管控

在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

在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
}

在其他需使用的组件里直接调用即可

/* 新建了一个非常简易的模板,便于理解 */
<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封装

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

引入( 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,请参考官方文档

环境的切换

我们的项目环境可能有开发环境、测试环境和生产环境。我们通过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'
}

设置请求超时

通过axios.defaults.timeout设置默认的请求超时时间。例如超过了10s,就会告知用户当前请求超时,请刷新等。

axios.defaults.timeout = 10000;

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'

请求拦截

我们在发送请求前可以进行一个请求的拦截,为什么要拦截呢,我们拦截请求是用来做什么的呢?比如,有些请求是需要用户登录之后才能访问的,或者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,但是后台可以选择不接收啊!

响应拦截

// 响应拦截器
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库,对应使用你的一个提示组件。

封装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的第二个参数就是一个参数对象。两者略微的区别要留意哦!

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

总结

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

你的赞是我前进的动力

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