前言:vue.js 2.0版本推荐使用axios来完成ajax请求。Axios是一个基于Promise的HTTP库,可以用在游览器和node.js中。
axios的封装和api接口的统一管理,其实主要目的就是在帮助我们简化代码和利于后期的更新维护。
在vue项目中,和后台交互获取数据这块,通常使用axios库,它是基于promise的http库,可运行在游览器端和node.js中。它有很多优秀的特性,例如拦截请求和响应、取消请求、转换json、客户端防御XSRF等。
axios具有以下特性:
①从游览器中创建XMLHttpRequest;
②从node.js发出http请求;
③支持Promise API;
④拦截请求和响应;
⑤转换请求和响应数据;
⑥取消请求;
⑦自动转换JSON数据;
⑧客户端支持防止CSRF/XSRF。
一、安装方法
1.使用CDN:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
2.使用npm:
$ npm install axios
3.使用bower:
$ bower install axios
4.使用yarn:
$ yarn add axios
二、axios封装步骤
引入
一般,在项目的src目录中,新建一个network文件夹,作为我们的网络请求模块,然后,在里面新建一个http.js和一个api.js文件和一个request.js;http.js文件用来封装我们的axios,app.js用来统一管理我们的接口url,request.js对外暴露我们放在的api方法。
//在http.js中引入axios
import axios from 'axios'; //引入axios
import QS from 'qs'; // 引入qs模块,用来序列化post类型的数据,后面会提到
import router from '../router';
import { Message } from "element-ui";
import store from "@/store";
import { getToken, setToken } from "@/utils/auth";
环境的切换
我们项目的环境可能有开发环境、测试环境和生产环境。通过node的环境变量来匹配,我们默认的接口url前缀。axios.defaults.baseURL可以设置axios的默认请求地址。
创建config目录:
目录下创建env.development.js和env.production.js+env.test.js
en.development.js内容如下:
module.exports={
baseUrl:'http://www.....com' //开发环境用到的baseUrl
}
//环境的切换
const {baseUrl}=require('../config/env.'+process.env.NOE_ENV);
//同时package.json的script需指定测试环境的模式 --mode test
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test": "vue-cli-service build --mode test",
"lint": "vue-cli-service lint"
}
const service = axios.create({
baseURL: baseUrl, // url = base api url + request url
withCredentials: false, // send cookies when cross-domain requests
timeout: 1000*12 // 请求超时
})
如上,设置请求超时
设置请求超时
通过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请求的时候,我们需要序列化我们提交的数据。这时候,我们可以在请求被发送之前进行一个拦截,从而进行我们想要的操作。
请求拦截
//创建实例
// create an axios instance
const service = axios.create({
baseURL: `${process.env.VUE_APP_BASE_API}`,
timeout: 10000,
headers: {
"X-Requested-With": "XMLHttpRequest",
"Content-Type": "application/json;charset=utf-8"
}
});
//先导入vuex,因为我们要使用到里面的状态对象
import store from "@/store";
import { getToken, setToken } from "@/utils/auth";
//请求拦截器
// request interceptor
service.interceptors.request.use(
config => {
const url = config.url.split(config.baseURL).join("");
const token = getToken();
if (whiteUrl.indexOf(url) < 0) {
if (config.url.indexOf('cmp') < 0) {
config.url = `${config.baseURL}/${store.getters["app/productItem"]["api"]}${url}`;
}
}
//每次发送请求之前,判断token是否存在
//如果存在,则统一在http请求的header都加上token,这样后台根据tokne判断你的登录情况
//即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
if (token) {
config.headers["authorization"] = token;
}
return config;
},
error => {
// do something with request error
// console.log(error); // for debug
return Promise.reject(error);
}
);
这里说明一下token,一般是在登录完成之后,将用户的token通过localStorage或者cookie存在本地,然后用户每次进入页面的时候(即在main.js中),会首先从本地存储中读取token,如果token存在说明用户已经登录过,则更新token的状态。然后,在每次请求接口的时候,都会在请求的header中携带token,后台人员就可以根据你携带的token来判断你的登陆是否过期,如果没有携带,则说明没有登录过。这时候或许有些小伙伴会有疑问了,就是每个请求都携带token,那么要是一个页面不需要用户登录就可以访问的怎么办呢?其实,前端的请求可以携带token,但是后台可以选择不接收啊。
补充一下auth.js中获取token的方法:
import Cookies from 'js-cookie'
const TokenKey = 'lanshan_token'
const UserInfoKey = 'lanshan_user'
const ProductKey = 'product_key'
const ExpiresTime = 1 / 4
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token, {
expires: ExpiresTime
})
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
export function getUserName() {
return Cookies.get(UserInfoKey)
}
export function setUserName(name) {
return Cookies.set(UserInfoKey, name, {
expires: ExpiresTime
})
}
export function removeUserName() {
return Cookies.remove(UserInfoKey)
}
export function getProductKey() {
return Cookies.get(ProductKey)
}
export function setProductKey(name) {
return Cookies.set(ProductKey, name, {
expires: ExpiresTime
})
}
export function removeProductKey() {
return Cookies.remove(ProductKey)
}
响应的拦截
// 响应拦截器
service.interceptors.response.use(
response => {
// console.log("response", response);
const res = response.data;
const code = Number(res.code);
let baseUrl = `${response.config.baseURL}`;
if (response.config.url.indexOf('cmp') < 0) {
baseUrl = `${response.config.baseURL}/${store.getters["app/productItem"]["api"]}`;
}
const downloadUrl = response.config.url.split(baseUrl).join("");
if (downloadUrlList.indexOf(downloadUrl) >= 0) {
return response;
}
//与后台开发人员协商好统一的错误状态码,根据返回的状态码进行一些操作,例如登录过期,错误提示等...
if (code !== 0) {
if (code === 401) {//登录失效,退出登录
Message({
message: "登录失效,请重新登录",
type: "error",
duration: 3 * 1000
});
store.dispatch("user/logout").then(() => {
location.reload();
});
}else if (code === 403) {//403token过期
Message({
message: "登录过期,请重新登录",
type: "error",
duration: 3 * 1000
});
store.dispatch("user/logout").then(() => {
location.reload();
});
// 清除本地token和清空vuex中token对象
localStorage.removeItem('token');
store.commit('loginSuccess', null);
}else {
Message({
message: res.message || "接口请求出错",
type: "error",
duration: 3 * 1000
});
}
return Promise.reject(new Error(res.message || "接口请求出错"));
}
if (response.headers.authorization) {
setToken(response.headers.authorization);
}
return res;
},
error => {
const originalRequest = error.config;
if (error.code === "ECONNABORTED" && error.message.indexOf("timeout") !== -1 && !originalRequest._retry) {
Message({
message: "网络开小差了~",
type: "error",
duration: 5 * 1000
});
return Promise.reject("网络开小差了~");
}
Message({
message: error,
type: "error",
duration: 5 * 1000
});
return Promise.reject(error);
}
);
export default service;
响应拦截器很好理解,就是服务器返回给我们的数据,我们在拿到之前可以对它进行一些处理。如:如果后台返回的状态码是200,则正常返回数据,否则,根据错误的状态码类型进行一些我们需要的错误,其实这里主要就是进行了错误的统一处理和没登录,或登录过期后调整登录页的一个操作。
封装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模块来序列化我们的参数。这个很重要,如果没有序列化操作,后台是拿不到你提交的数据的。
/**
* 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文件夹,里面有一个index.js,以及多个根据模块划分的接口js文件。index.js是一个api的出口,其他js则用来管理各个模块的接口。
例如:下面的article.js
/**
* article模块接口列表
*/
import request from '@/netword/http'; //导入http中创建的axios实例
import qs from 'qs'; //根据需求是否导入qs模块
const article = {
// 新闻列表
articleList () {
return request({
url: '/artical',
method: 'get',
params,
})
},
// 新闻详情,演示
articleDetail (id, params) {
return request({
url: '/detail',
method: 'get',
params:{
goodsId
},
})
},
// post提交
login (data) {
return request({
url:'/adduser',
method:'post',
data:qs.stringify(data), //注意post提交用data参数
})
}
// 其他接口…………
}
export default article;
index.js代码:
/**
* api接口的统一出口
*/
// 文章模块接口
import article from '@/api/article';
// 其他模块的接口……
// 导出接口
export default {
article,
// ……
}
在组件中的使用(按需导入)
import {article} from '@/api/index'
created(){
article.articleList().then(info=>{
if(info.code==200){
this.num=info.data
}
})
}
api挂载到vue.prototype上省去引入的步骤
为了方便api的调用,我们需要将其挂载到vue的原型上。在main.js中:
import Vue from 'vue'
import App from './App'
import router from './router' // 导入路由文件
import store from './store' // 导入vuex文件
import api from './api' // 导入api接口
Vue.prototype.$api = api; // 将api挂载到vue的原型上复制代码
然后,我们在组件中可以这么用:
//无需导入
methods: {
onLoad(id) {
this.$api.article.articleDetail(id, {
api: 123
}).then(res=> {
// 执行某些操作
})
}
}
断网情况处理
如下app.vue新增:
<template>
<div id="app">
<div v-if="!network">
<h3>我没网了</h3>
<div @click="onRefresh">刷新</div>
</div>
<router-view/>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'App',
computed: {
...mapState(['network'])
},
methods: {
// 通过跳转一个空页面再返回的方式来实现刷新当前页面数据的目的
onRefresh () {
this.$router.replace('/refresh')
}
}
}
</script>
这是app.vue,这里简单演示一下断网。在http.js中介绍了,我们会在断网的时候,来更新vue中network的状态,那么这里我们根据network的状态来判断是否需要加载这个断网组件。断网情况下,加载断网组件,不加载对应页面的组件。当点击刷新的时候,我们通过跳转refesh页面然后立即返回的方式来实现重新获取数据的操作。因此我们需要新建一个refresh.vue页面,并在其beforeRouteEnter钩子中再返回当前页面。
// refresh.vue
beforeRouteEnter (to, from, next) {
next(vm => {
vm.$router.replace(from.fullPath)
})
}
参考博客:
axios封装与api接口管理 https://zhuanlan.zhihu.com/p/262665309
vue中Axios的封装和API接口的管理 https://zhuanlan.zhihu.com/p/135965633
Vue之axios基础使用 https://www.jianshu.com/p/4ee31fdb78b6
Vue.js Ajax(axios) https://www.runoob.com/vue2/vuejs-ajax-axios.html