「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战」。
前言
开门见山,本篇文章,将分享一下自己平时在工作中常应用到的http请求的封装思路和方法。由于笔者工作中最常用的框架是Vue,自然而然,本文是基于axios进行展开的
为什么要对axios进行封装
可能有同学会问到:“axios明明已经非常好了,功能都非常齐全了,要啥有啥,为什么还要花时间费劲封装多一次,要什么自行车?”
对于提出这种问题的同学,我只能说:“问得好,看完文章之后,下次不要再问了”
思考以下几个问题:
- 如果要统一设置请求头,你会怎么做
- 如果要针对特定接口拦截返回值并处理数据,你会怎么做
- 如果要统一设置loading动画等,你会怎么做
不难看出,以上的问题有一个共同点,就是 统一处理 ,这也就是为什么需要进行封装的原因了
怎么封装
工程目录结构
首先我们会在项目工程目录下创建一个 service的文件夹 用来存放请求类,然后又在文件夹内创建一个 http的文件夹 用来存放 构造器、拦截器、请求出口 ,而至于 aip1文件夹 这个便是用于存放具体请求的地方了(可以以接口划分,也可以以页面结构划分,也可以以组件结构划分,具体以实际项目为准)
详细讲解
拦截器
从上面截图可以看到,interceptors.js 文件就是用来存放拦截器的,拦截器的作用是什么呢
在拦截器中,你可以针对请求方法、请求参数、接口回调参数、请求异常、响应异常等做统一处理
回到本文开头的三个问题,问题2 3 即可在拦截器内进行处理
// service/http/interceptors.js
// http请求拦截器
const request = config => {
// 这里实现http请求全局拦截 config为axios请求配置项
if (config.method === 'get') {
// do something
}
if (config.method === 'post') {
// do something
}
return config;
};
const requestError = err => {
const { config, code, message } = err;
// 在此可以做一些异常监控 譬如错误接口,错误信息等收集 用于线上服务排查
// do something
// 因为axios是返回promise实体,故在错误拦截中,返回空数据,确保接口响应无异常
return Promise.reject({ code, data: {} });
};
// http响应拦截器
// 响应拦截器一般配合缓存策略 对接口进行缓存时使用
const response = res => {
const { config, data } = res;
// do something
return res.data;
};
const responseError = err => {
const { config, code, message } = err;
// 在此可以做一些异常监控 譬如错误接口,错误信息等收集 用于线上服务排查
// do something
// 这里也可以配合业务缓存策略 对错误接口调用缓存数据,确保服务正常
// do something
// 针对不同接口是否需要调用缓存数据 返回不同实体
// return data (data取自缓存)
return Promise.reject({ code, data: {} });
};
export default {
request,
requestError,
response,
responseError
};
拦截器生产中常发挥到的用途,已在代码块及上文提及,更多用法可以拓展一下结合业务(肯定可以玩得很离谱)
构造器
顾名思义,构造器,即是构造请求实例的类啦
在构造器中,我们可以设置业务中所需的接口域名,并将拦截器的实例传入axios实例方法中
值得注意的是,虽然我们也可以在构造器中对请求方法进行业务处理,但是为了保证单一原则,一物只做一事,所以在构造器里,还是让它本本分分构造axios实例就好了
// service/http/fetch.js
import axios from 'axios';
import intercepors from './interceptors';
// 创建不同axios实体
let fetch1 = createFetchByHost('//host1.cn', true);
let fetch2 = createFetchByHost('//host2.cn', false);
// axios实体构造函数 可以根据业务需求自行修改
/**
* 创建fetch
* @param {string} url
*/
function createFetchByHost(url, withCredentials = false) {
let http = axios.create({
baseURL: process.env.NODE_ENV === 'production' ? url : '',
timeout: 5000,
withCredentials: withCredentials,
headers: { post: { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' } }
});
// 配置请求头公共部分
http.interceptors.request.use(intercepors.request, intercepors.requestError);
http.interceptors.response.use(intercepors.response, intercepors.responseError);
return http;
}
export default { fetch1, fetch2 };
export { fetch1, fetch2 };
请求出口
请求出口就没什么好讲的了,它所做的事情仅仅是把构造器构造的axios实例暴露出去,直接贴示例代码
// service/http/index.js
import { fetch1, fetch2 } from './fetch';
class Api {
constructor() {
this.fetch1 = fetch1;
this.fetch2 = fetch2;
}
}
export default Api;
export { Api };
业务接口封装
我们在上面讲解了拦截器、构造器、请求出口,基本上一个半成品就快出来了,现在我们基于半成品,对特定业务接口再封装一下,便大功告成
我们在构造器中,针对不同域名构造了不同的axios实例,在这里,我们将应用这些实例,再构造出特定的接口类。这样,我们就可以针对特定的接口进行统一处理
文章开头的问题3 即可以在这里进行处理。除此之外,我们还可以结合拦截器,对指定域名做接口缓存等
// service/api1/index.js
import Api from '../http';
class Api1 extends Api {
async apiA({ params = '' }) {
return this.fetch1.post('/api/path', { params }).catch(() => ({ result: 'error', data: {} }));
}
}
export default new Api1();
service类出口
这里也是封装的最后一步,将我们上述的步骤整完之后,将service类暴露出去提供给业务使用
// service/index.js
import { fetch } from './http';
import api1 from './api1';
const install = VUE => {
VUE.prototype.$http = fetch;
VUE.prototype.$httpApi1 = api1;
};
export default { api1, install };
export { api1 as api1Service, install };
在暴露service类的时候,我们将axios实例和业务接口实例赋值给了vue原型属性,这个根据业务进行调整,我个人倾向是这样干的,毕竟比较方便...
如何在应用
怎么应用这事,就没必要花过多笔墨了,直接贴个示例吧
/*****调用*****/
// 在引用vue实例的组件内
// this.$httpApi1.apiA()
// 在纯js方法内
// import { api1Service } from '@/service';
// api1Service.apiA()