前言
在 typescript
的大环境下,我们前期通过 vue-cli
脚手架创建了新项目,这时需要用到 axios
对接后端接口。那么这种情况下,我们从网上搜索到相关的文档及博客去做进一步封装。
我也从中学到了许多,也将其中不错的部分提炼并用在自己的项目中,后续还会继续改进,可以供大家参考。
其中,注意的事项点写在代码的备注中。
Axios的封装
整理思路
index.ts
我们需要在src的根目录下创建一个axios文件夹,其中创建一个index.ts文件,这个文件主要用来封装axios的配置(实例化请求配置、请求拦截器、相应拦截器)及相应的方法(登录跳转、消息提示、错误处理等)
base.ts
这个文件主要用于项目扩展的情况下 不同模块需要调用不同接口(请求的base地址 baseURL
)而前期做的准备,便于后期的维护
request.ts
主要用于封装基于axios配置的get/post/put/delete等使用方法。
api.ts
在后面的 main.ts
中引入该模块,包括所有接口数据信息写入该文件中。
目录结构
-- src
-- axios
index.ts
base.ts
request.ts
api.ts
代码如下:
index.ts
提示工具用到了element-ui
,封装如下。考虑到单一职责,index这块只封装axios就足矣。
// index.ts
import axios from "axios";
import router from "@/router";
import store from "@/store";
import { Message } from "element-ui";
const message = (msg: string,type?: any) => {
Message({
message: msg,
type: type || 'warning',
duration:1500,
});
}
/**
* 跳转登录页
* 携带当前页面路由,以期在登录页面完成登录后返回当前页面
*/
const toLogin = () => {
router.replace({
name: 'LoginPage',
});
}
/**
* 请求失败后的错误统一处理
* @param {Number} status 请求失败的状态码
*/
const errorHandle = (status: number, other: string) => {
// 状态码判断
switch (status) {
// -1: 未登录状态,跳转登录页
case -1:
toLogin();
break;
// 403 token过期
// 清除token并跳转登录页
case 403:
message('登录过期,请重新登录');
localStorage.removeItem('token');
store.commit('token', null);
setTimeout(() => {
toLogin();
}, 1000);
break;
// 404请求不存在
case 404:
message('请求的资源不存在');
break;
default:
message(other);
}
}
/* 实例化请求配置 */
const instance = axios.create({
headers: {
//php 的 post 传输请求头一定要这个 不然报错 接收不到值
"Content-Type": "application/x-www-form-urlencoded",
},
// 请求时长
timeout: 1000 * 30,
// 请求的base地址 TODO:这块以后根据不同的模块调不同的api
// baseURL:
// process.env.NODE_ENV === "development"
// ? "测试"
// : "正式",
withCredentials: true,
})
/**
* 请求拦截器
* 每次请求前,如果存在token则在请求头中携带token
*/
instance.interceptors.request.use(
config => {
// 登录流程控制中,根据本地是否存在token判断用户的登录情况
// 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token
// 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码
// 而后我们可以在响应拦截器中,根据状态码进行一些统一的操作。
const token = store.state.token;
localStorage.setItem('token', token);
token && (config.headers.Authorization = token)
return config;
},
// error => Promise.error(error)
)
// 响应拦截器
instance.interceptors.response.use(
// 请求成功
res => res.status === 200 ? Promise.resolve(res.data) : Promise.reject(res),
// 请求失败
error => {
const { response } = error;
if (response) {
// 请求已发出,但是不在2xx的范围
errorHandle(response.status, response.data.message);
return Promise.reject(response);
} else {
// 处理断网的情况
// eg:请求超时或断网时,更新state的network状态
// network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
// 后续增加断网情况下做的一些操作
store.commit('networkState', false);
...
}
}
)
// 只需要考虑单一职责,这块只封装axios
export default instance
base.ts
区分每个模块的 baseUrl 方便后期维护管理
// base.ts
export default class Base {
/* 公共模块 */
static common = process.env.NODE_ENV === "development"
? "https://testCommon.com(测试线地址)"
: "https://produceCommon.com(生产线地址)"
/* 活动模块 */
static active = process.env.NODE_ENV === "development"
? "https://testActive.com(活动测试线地址)"
: "https://produceActive.com(活动生产线地址)"
}
request.ts
封装axios的get、post方法,其余关于接口调用的方法也可写入该文件中,便于管理。
// request.ts
import axios from "./index";
import qs from "qs";
export default class Request{
/**
* get方法
* @param {string} url 路径
* @param {object} params 参数
*/
static get = (url: string, params?: object) => {
return new Promise((resolve, reject) => {
axios.get(url, { params: params }).then(res => {
resolve(res);
}).catch(err => {
reject(err);
})
})
}
static post = (url: string, params?: object) => {
return new Promise((resolve, reject) => {
axios.post(url, qs.stringify(params)).then(res => {
resolve(res);
}).catch(err => {
reject(err);
})
})
}
}
api.ts
其中使用 install
的目的在于 ts 在main.ts
中 不能通过Vue.prototype.$Api
这个方式直接调用,在全局方法中会说到使用 插件的方式去挂载。
// api.ts
import base from "./base";
import request from "./request";
class Api {
/* 公共模块 */
public static common = {
genre: (params = {}) => request.get(`${base.common}frontend/genre`, params),
}
/* 活动模块 */
public static active = {
}
}
const protoApi = {
install: (Vue: any) => {
Vue.prototype.$Api = Api
}
}
export default protoApi
全局挂载
在上述的 api.ts
中我们看到需要用到插件的方式去全局挂载,整个流程步骤我会在如下的代码中粘贴出来。
一、在 api.ts
中导出
// api.ts
import base from "./base";
import request from "./request";
class Api {
/* 公共模块 */
public static common = {
genre: (params = {}) => request.get(`${base.common}frontend/genre`, params),
}
/* 活动模块 */
public static active = {
}
}
const protoApi = {
install: (Vue: any) => {
Vue.prototype.$Api = Api
}
}
export default protoApi
二、在 main.ts
中导入
// main.ts
import api from "./axios/api";
// 由于api该模块已经注册成组件了,所以main中直接使用该组件便可
Vue.use(api)
三、在src的根目录下创建一个ts为 vue-prototype.d.ts
,该命名方式为 *.d.ts
,无需一样。
-- src
-- vue-prototype.d.ts
// vue-prototype.d.ts
import Vue from "vue";
import api from "./axios/api";
declare module "vue/types/vue" {
interface Vue {
$Api:api;
}
}
四、最后在我们的模版中调用该方法即可。
注:如下看到 $Api 方法在 eslint 下报错 Property $Api does not exist on type 等字眼,只需重启下vscode编译器即可。
// LoginPage.vue
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component({
name: 'LoginPage',
})
export default class LoginPage extends Vue {
mounted(): void {
this.$Api.common.genre().then((res: object) => {
console.log("公共方法",res)
})
// 活动
this.$Api.active.xxx().then()
}
}
</script>
总结
在封装的过程中 我们总会遇到一些坑,不过好在看了挺多大部分网上不错的解决方案,结合自己的思路也是可以做到的。