axios
是一个轻量的HTTP客户端
,它基于XMLHttpRequest
服务来执行 HTTP 请求,支持丰富的配置,支持Promise
,支持浏览器端和Node.js
端。自Vue2.0起,尤大大(Vue做者尤雨溪)宣布取消对vue-resource
的官方推荐,转而推荐axios
。如今axios
已经成为大部分 Vue 开发者的首选。
(若是你还不熟悉axios
,能够在 这里查看它的API)。
axios
的API很友好,你彻底能够很轻松地在项目中直接使用。不过随着项目规模增大,若是每发起一次HTTP请求,就要把这些好比设置超时时间、设置请求头、根据项目环境判断使用哪一个请求地址、错误处理等等操做,都就地写一遍,得疯!这种重复劳动不只浪费时间,并且让代码变得冗余不堪,难以维护。前端
为了提升咱们的代码质量,咱们应该在项目中二次封装一下 axios
再使用。vue
那么,怎么封装 axios
呢?ios
封装前,先来看下,不封装的状况下,一个实际项目中axios请求的样子。大概是长这样:git
axios('http://localhost:3000/data', { method: 'GET', timeout: 1000, withCredentials: true, headers: { 'Content-Type': 'application/json', Authorization: 'xxx', }, transformRequest: [function (data, headers) { return data; }], // 其余请求配置... }) .then((data) => { // todo: 真正业务逻辑代码 console.log(data); }, (err) => { if (err.response.status === 401) { // handle authorization error } if (err.response.status === 403) { // handle server forbidden error } // 其余错误处理..... console.log(err); });
能够看到在这段代码中,页面代码逻辑只在第15行处,上方的一大块请求配置代码和下方一大块响应错误处理代码,几乎跟页面功能没有关系,并且每一个请求中这些内容都差很少,甚至有的部分彻底同样。想象一下,每发一次请求都来这么一下,十几个请求一写,会是什么盛况?github
封装的本质就是在待封装的内容外面添加各类东西,而后把它们做为一个新的总体呈现给使用者,以达到扩展和易用的目的。npm
封装axios
要作的事情,就是把全部HTTP请求共用的配置,事先都在axios上配置好,预留好必要的参数和接口,而后把它做为新的axios返回。json
接下来咱们借助一个demo实现一个具备良好扩展性的axios
封装。axios
demo目录结构以下(由Vue-cli 3.0 生成):api
|--public/ |--mock/ | |--db.json # 我新建的接口模拟数据 |--src/ | |--assets/ | |--components/ | |--router/ | |--store/ | |--views/ | |--Home.Vue | |--App.vue | |--main.js | |--theme.styl |--package.json |...
我但愿在 Home 页,发起 axios 请求时就像调用一个只有少许参数的方法同样简单,这样我就能够专一业务代码了。promise
cd src mkdir utils touch http.js
// src/utils/http.js import axios from 'axios';
//src/utils/http.js //... class NewAxios { }
根据 process.env.NODE_ENV
配置不一样的 baseURL
,使项目只需执行相应打包命令,就能够在不一样环境中自动切换请求主机地址。
// src/utils/http.js //... const getBaseUrl = (env) => { let base = { production: '/', development: 'http://localhost:3000', test: 'http://localhost:3001', }[env]; if (!base) { base = '/'; } return base; }; class NewAxios { constructor() { this.baseURL = getBaseUrl(process.env.NODE_ENV); } }
timeout属性,我通常设置10秒。
// src/utils/http.js //... class NewAxios { constructor() { //... this.timeout = 10000; } }
widthCredentials属性设为true。
// src/utils/http.js //... class NewAxios { constructor() { //... this.withCredentials = true; } }
在 request
方法里,建立新的axios实例,接收请求配置参数,处理参数,添加配置,返回axios实例的请求结果(一个promise对象)。
你也能够不建立,直接使用默认导出的axios实例,而后把全部配置都放到它上面,不过这样一来整个项目就会共用一个axios实例。虽然大部分项目下这样够用没问题,可是有的项目中不一样服务地址的请求和响应结构可能彻底不一样,这个时候共用一个实例就没办法支持了。因此为了封装能够更通用,更具灵活性,我会使用axios的create方法,使每次发请求都是新的axios实例。
// src/utils/http.js //... class NewAxios { //... request(options) { // 每次请求都会建立新的axios实例。 const instance = axios.create(); const config = { // 将用户传过来的参数与公共配置合并。 ...options, baseURL: this.baseURL, timeout: this.timeout, withCredentials: this.withCredentials, }; // 配置拦截器,支持根据不一样url配置不一样的拦截器。 this.setInterceptors(instance, options.url); return instance(config); // 返回axios实例的执行结果 } }
由于拦截器配置内容比较多,因此封装成一个内部函数了。
// src/utils/http.js //... class NewAxios { //... // 这里的url可供你针对须要特殊处理的接口路径设置不一样拦截器。 setInterceptors = (instance, url) => { instance.interceptors.request.use((config) => { // 请求拦截器 // 配置token config.headers.AuthorizationToken = localStorage.getItem('AuthorizationToken') || ''; return config; }, err => Promise.reject(err)); } //... }
then
或catch
处理前对响应数据进行一轮预先处理。好比过滤响应数据,更多的,是在这里对各类响应错误码进行统一错误处理,还有断网处理等等。// src/utils/http.js //... class NewAxios { //... setInterceptors = (instance, url) => { //... instance.interceptors.response.use((response) => { // 响应拦截器 // todo: 想根据业务须要,对响应结果预先处理的,都放在这里 console.log(); return response; }, (err) => { if (err.response) { // 响应错误码处理 switch (err.response.status) { case '403': // todo: handler server forbidden error break; // todo: handler other status code default: break; } return Promise.reject(err.response); } if (!window.navigator.online) { // 断网处理 // todo: jump to offline page return -1; } return Promise.reject(err); }); } //... }
另外,在拦截器里,还适合放置loading等缓冲效果:在请求拦截器里显示loading,在响应拦截器里移除loading。这样全部请求就都有了一个统一的loading效果。
// src/utils/http.js //... export default new NewAxios();
最后完整的代码以下:
// src/utils/http.js import axios from 'axios'; const getBaseUrl = (env) => { let base = { production: '/', development: 'http://localhost:3000', test: 'http://localhost:3001', }[env]; if (!base) { base = '/'; } return base; }; class NewAxios { constructor() { this.baseURL = getBaseUrl(process.env.NODE_ENV); this.timeout = 10000; this.withCredentials = true; } setInterceptors = (instance, url) => { instance.interceptors.request.use((config) => { // 在这里添加loading // 配置token config.headers.AuthorizationToken = localStorage.getItem('AuthorizationToken') || ''; return config; }, err => Promise.reject(err)); instance.interceptors.response.use((response) => { // 在这里移除loading // todo: 想根据业务须要,对响应结果预先处理的,都放在这里 return response; }, (err) => { if (err.response) { // 响应错误码处理 switch (err.response.status) { case '403': // todo: handler server forbidden error break; // todo: handler other status code default: break; } return Promise.reject(err.response); } if (!window.navigator.online) { // 断网处理 // todo: jump to offline page return -1; } return Promise.reject(err); }); } request(options) { // 每次请求都会建立新的axios实例。 const instance = axios.create(); const config = { // 将用户传过来的参数与公共配置合并。 ...options, baseURL: this.baseURL, timeout: this.timeout, withCredentials: this.withCredentials, }; // 配置拦截器,支持根据不一样url配置不一样的拦截器。 this.setInterceptors(instance, options.url); return instance(config); // 返回axios实例的执行结果 } } export default new NewAxios();
如今 axios
封装算是完成了80%。咱们还须要再进一步把axios和接口结合再封装一层,才能达到我在一开始定的封装目标。
api
文件夹。把全部涉及HTTP请求的接口统一集中到这个目录来管理。home.js
。咱们须要把接口根据必定规则分好类,一类接口对应一个js文件。这个分类能够是按页面来划分,或者按模块等等。为了演示更直观,我这里就按页面来划分了。实际根据本身的需求来定。// src/api/home.js import axios from '@/utils/http'; export const fetchData = options => axios.request({ ...options, url: '/data', }); export default {};
index.js
,把其余文件的接口都在这个文件里汇总导出。// src/api/index.js export * from './home';
这层封装将咱们的新的axios封装到了更简洁更语义化的接口方法中。
如今咱们的目录结构长这样:
|--public/ |--mock/ | |--db.json # 接口模拟数据 |--src/ | |--api/ # 全部的接口都集中在这个目录下 | |--home.js # Home页面里涉及到的接口封装在这里 | |--index.js # 项目中全部接口调用的入口 | |--assets/ | |--components/ | |--router/ | |--store/ | |--utils/ | |--http.js # axios封装在这里 | |--views/ | |--Home.Vue | |--App.vue | |--main.js | |--theme.styl |--package.json |...
如今咱们要发HTTP请求时,只需引入 api
下的 index.js
文件就能够调用任何接口了,而且用的是封装后的 axios
。
// src/views/Home.vue <template> <div class="home"> <h1>This is home page</h1> </div> </template> <script> // @ is an alias to /src import { fetchData } from '@/api/index'; export default { name: 'home', mounted() { fetchData() // axios请求在这里 .then((data) => { console.log(data); }) .catch((err) => { console.log(err); }); }, }; </script>
axios请求被封装在fetchData
函数里,页面请求压根不须要出现任何axios API
,悄无声息地发起请求获取响应,就像在调用一个简单的 Promise
函数同样轻松。而且在页面中只需专一处理业务功能,不用被其余事物干扰。
运行 npm run serve
启动项目,执行 npm run mock
启动服务mock接口。
如今打开 localhost:8080
能够看到home页面。打开浏览器控制台,能够看到打印的请求响应结果:
简洁,优雅。
axios
没有一个绝对的标准,只要你的封装能够知足你的项目需求,而且用起来方便,那就是一个好的封装方案。axios
,能够不局限于 Vue,React 项目一样能够拿去使用,它适用任何前端项目。本文的代码能够在这里获取:https://github.com/yc111/wrap...
欢迎交流~
欢迎转载,转载请注明出处:
https://champyin.com/2019/12/...