1.1 http 封装
http 封装其实就是实例化一个 axios 对象,并对其进行一些配置:
- 1、设置请求超时
- 2、post 头设置
- 3、请求拦截
- 4、响应拦截
- 5、重复请求取消(这里涉及到了如何取消请求)
- 6、错误处理
- 7、断网处理
- 8、工具函数
import axios from 'axios'
import router from '../router'
import store from '../store/index'
import { Toast } from 'vant'
//提示函数
const tip = msg => {
Toast({
message: msg,
duration: 1000,
forbidClick: true
})
}
//跳转登录页,携带当前页面路由,登录后返回当前页面
const toLogin = () => {
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
}
//错误处理
const errorHandle = (status, other) => {
switch (status) {
//未登录
case 401:
toLogin();
break;
//403 token过期,清除token并跳转登录页
case 403:
tip('登录过期,请重新登录');
localStorage.removeItem('token');
store.commit(loginSuccess, null);
setTimeout(() => {
toLogin();
}, 1000);
break;
//请求不存在
case 404:
tip('请求资源不存在');
break;
default:
console.log(other)
}
}
// 用于存储目前状态为pending的请求标识信息
let pendingRequest = [];
//取消请求-请求拦截中的处理
const CancelToken = config => {
// 区别请求的唯一标识,这里用方法名+请求路径
const requestMark = `${config.method} ${config.url}`;
// 找当前请求的标识是否存在pendingRequest中,即是否重复请求了
const markIndex = pendingRequest.findIndex(item => {
return item.name === requestMark;
});
// 存在,即重复了
if (markIndex > -1) {
// 取消上个重复的请求
pendingRequest[markIndex].cancel();
// 删掉在pendingRequest中的请求标识
pendingRequest.splice(markIndex, 1);
}
// (重新)新建针对这次请求的axios的cancelToken标识
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
config.cancelToken = source.token;
// 设置自定义配置requestMark项,主要用于响应拦截中
config.requestMark = requestMark;
// 记录本次请求的标识
pendingRequest.push({
name: requestMark,
cancel: source.cancel,
});
return config;
};
//取消请求-响应拦截中的处理
const CancelTokenResponse = config => {
// 根据请求拦截里设置的requestMark配置来寻找对应pendingRequest里对应的请求标识
const markIndex = pendingRequest.findIndex(item => {
return item.name === config.requestMark;
});
// 找到了就删除该标识
markIndex > -1 && pendingRequest.splice(markIndex, 1);
}
//创建axios实例
var instance = axios.create({
timeout: 1000 * 12
})
//设置post请求头
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
//请求拦截器
instance.interceptors.request.use(
config => {
config = CancelToken(config);
const token = store.state.token;
token && (config.headers.Authorization = token);
return config;
},
error => Promise.reject(error);
)
//响应拦截器
instance.interceptors.response.use(
res => {
//请求结束就从pendingRequest删除请求标志
CancelTokenResponse(res.config);
return res.status === 200 ? Promise.resolve(res) : Promise.reject(res);
},
error => {
const { response } = error;
//请求已经发出,返回结果不在2xx的范围
if (response) {
CancelTokenResponse(response.config);
errorHandle(response.status, response.data.message);
return Promise.reject(response);
} else {
//断网情况,刷新重新获取数据
if (!window.navigator.onLine) {
store.commit('changeNetwork', false);
} else {
return Promise.reject(error);
}
}
}
)
export default instance;
1.2 api 封装
api 封装主要是用于单个模块所需要的接口进行管理。其中包括了
- 1、总 api 接口的映射
- 2、环境变量的切换
- 3、本地 mock 功能
- 4、单个模块的接口列表
1.2.1 总 api 接口的映射
api 中定义一个 index.js 用于所有模块接口的管理
//api接口统一出口
import user from "./user";
import productList from "./product";
//...
export { user, productList };
1.2.2 建立一个 get_url.js,用于获取域名地址,实现环境切换可配置
import config from "@/config";
import urlMap from "@/config/urlMap";
//api控制接口类型,1为远程接口,0为mock接口
export default function getUrl(url, api = 1) {
return api === 0 ? urlMap[url] : config.apiDomain + url;
}
新建一个配置文件夹 config,其中的 index 文件用于切换环境,urlMap 用于本地 mock 地址的映射
//config/index.js
/**
* 配置编译环境和线上环境之间的切换
* 默认三套可以增添
*/
let apiDomain;
switch (process.env.NODE_ENV) {
case "dev":
apiDomain = "https://www.dev.com";
break;
case "prod":
apiDomain = "https://www.prod.com";
break;
case "test":
apiDomain = "https://www.test.com";
break;
}
export { apiDomain };
//urlMap.js
/**
* 远程接口地址和本地mock地址映射表
* key:接口地址
* value:本地地址
*/
const mockBaseUrl = "http://rap2api.taobao.org/app/mock";
export default {
"/user/login": mockBaseUrl + "/223948/login",
"/user/info": mockBaseUrl + "/223948/info",
"/user/logout": mockBaseUrl + "/223948/logout",
};
1.2.3 单个模块接口定义
//产品列表接口
import axios from "@/http";
import getUrl from "@/api/get_url";
const product = {
// 新闻列表
productList() {
return axios({
url: getUrl("/topics"),
method: "get",
});
},
// 新闻详情,演示
productDetail(id, params) {
return axios({
url: getUrl(`/topic/${id}`),
params: params,
method: "get",
});
},
// post提交
login(data) {
return axios({
url: getUrl(`${base.sq}/accesstoken`),
method: "post",
data,
});
},
//...更多接口
};
export default product;
1.2.4 断网处理
- 1、http 封装中,当断网时,会对 Vue 的网络状态进行更新
- 2、在 App.vue 中,根据网络情况,判断是否需要加载断网组件
- 3、全局定义一个断网组件,实现跳转重新获取页面的操作
<!-- 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>
http.js中断网的时候,会更新 vue 中 network 的状态。此时根据 network 的状态来判断是否需要加载这个断网组件。当点击刷新的时候,我们通过跳转 refesh 页面然后立即返回的方式来实现重新获取数据的操作。因此我们需要新建一个refresh.vue页面,并在其beforeRouteEnter钩子中再返回当前页面。
// refresh.vue
beforeRouteEnter (to, from, next) {
next(vm => {
vm.$router.replace(from.fullPath)
})
}
1.3 将 api 挂载到全局
//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的原型上
1.4 中断 axios 请求
上面避免重复请求中,就使用到了 cancel token 取消请求,使用 CancelToken 工厂方法创建 cancel token
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios
.get("/user/12345", {
cancelToken: source.token,
})
.catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log("Request canceled", thrown.message);
} else {
// 处理错误
}
});
axios.post(
"/user/12345",
{
name: "new name",
},
{
cancelToken: source.token,
}
);
// 取消请求(message 参数是可选的)
source.cancel("Operation canceled by the user.");