此次封装分为初级版和进阶版两部分,初级版可做练手使用。实际上,axios封装与否皆可做项目,在组件内正常发起请求也是最原始的方式,但随着前端的发展越来越快,项目体量的增大,必然会更加需要模块化的思想,在这里把axios看做一个发起请求的模块即可...
准备工作
- 首先,假设你的目录结构如图:
每个人可能有不同的代码目录,这个不同强求完全一致,理解封装思想即可。
至于每个目录结构是做什么的,传送门从零使用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文件夹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)
})
});
}
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流。
你的赞是我前进的动力
求赞,求评论,求分享...