https://juejin.im/post/684490365288107214
如图,直面一塌糊涂标识符的你~~~吗想说,What F~U~C~K!!!
重回自问自答,他们首先需要的说的axios的封装和apiUSB的标准化管理工作,只不过主要就目地是在协助他们精简标识符和有利于中后期的预览保护。
一、axios的PCB
tp库,可运转在应用程序端和node.js中。他有许多杰出的优点,比如截击允诺和积极响应、中止允诺、切换json、应用程序防卫XSRF等。因此他们的尤大幅也是主动出击舍弃了对其非官方库vue-resource的保护,间接所推荐他们采用axios库。假如还对axios不介绍的,能点选axios文件格式。
加装
npm install axios; // 加装axios导入
通常就要在工程项目地src产品目录中,增建两个request文件格式夹,接着在里头增建两个http.js和两个api.js文件格式。http.js文件格式用以PCB他们的axios,api.js用以标准化管理工作他们的USB。
// 在http.js中导入axiosimportaxiosfrom axios; // 导入axiosimport QS from qs; // 导入qs组件,用以格式化post类别的统计数据,前面会提及// vant的toast提示符组件,我们可依照他们的ui组件更动。import { Toast } from vant;
环境的切换
他们的工程项目环境可能有开发环境、测试环境和生产环境。他们通过node的环境变量来匹配他们的默认的USBurl前缀。axios.defaults.baseURL能设置axios的默认允诺地址就不多说了。
// 环境的切换if (process.env.NODE_ENV == development) {
axios.defaults.baseURL =https://www.baidu.com;}
else if (process.env.NODE_ENV == debug) {
axios.defaults.baseURL = https://www.ceshi.com;
}
else if (process.env.NODE_ENV == production) {
axios.defaults.baseURL = https://www.production.com;
}
设置允诺超时
通过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允诺的时候,他们需要格式化他们提交的统计数据。这时候,他们能在允诺被发送之前进行两个截击,从而进行他们想要的操作。
允诺截击
// 先导入vuex,因为他们要采用到里头的状态对象// vuex的路径依照他们的路径去写import store from @/store/index;
// 允诺截击器axios.interceptors.request.use( config =>{
// 每次发送允诺之前判断vuex中是否存在token // 假如存在,则标准化在http允诺的header都加上token,这样后台依照token判断你的登录情况 // 即使本地存在token,也有可能token是过期的,因此在积极响应截击器中要对返回状态进行判断 consttoken = store.state.token;
token && (config.headers.Authorization = token);
returnconfig;
},
error => {
return Promise.error(error);
})
这里说一下token,通常是在登录完成之后,将用户的token通过localStorage或者cookie存在本地,接着用户每次在进入页面的时候(即在main.js中),会首先从本地存储中读取token,假如token存在说明用户已经登陆过,则预览vuex中的token状态。接着,在每次允诺USB的时候,都会在允诺的header中携带token,后台人员就能依照你携带的token来判断你的登录是否过期,假如没有携带,则说明没有登录过。这时候或许有些小伙伴会有疑问了,是每个允诺都携带token,那么要是两个页面不需要用户登录就能访问的怎么办呢?只不过,你前端的允诺能携带token,但是后台能选择不接收啊!
积极响应的截击
// 积极响应截击器axios.interceptors.response.use(
response =>{
// 假如返回的状态码为200,说明USB允诺成功,能正常拿到统计数据 // 否则的话抛出错误 if (response.status === 200) {
return Promise.resolve(response);
} else{
return Promise.reject(response);
}
},
// 服务器状态码不是2开头的的情况 // 这里能跟你们的后台开发人员协商好标准化的错误状态码 // 接着依照返回的状态码进行一些操作,比如登录过期提示,错误提示等等 // 下面列举几个常见的操作,其他需求可自行扩展error => {
if(error.response.status) {
switch(error.response.status) {
// 401: 未登录 // 未登录则跳转登录页面,并携带当前页面的路径 // 在登录成功后返回当前页面,这一步需要在登录页操作。 case 401:
router.replace({
path: /login,
query: {
redirect: router.currentRoute.fullPath
}
});
break;
// 403 token过期 // 登录过期对用户进行提示 // 清除本地token和清空vuex中token对象 // 跳转登录页面 case 403:
Toast({
message: 登录过期,请重新登录,
duration: 1000,
forbidClick: true});
// 清除tokenlocalStorage.removeItem(token);
store.commit(loginSuccess, null);
// 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面 setTimeout(() =>{
router.replace({
path: /login,
query: {
redirect: router.currentRoute.fullPath
}
});
},1000);
break;
// 404允诺不存在 case 404:
Toast({
message: 网络允诺不存在,
duration: 1500,
forbidClick: true});
break;
// 其他错误,间接抛出错误提示 default:
Toast({
message: error.response.data.message,
duration: 1500,
forbidClick: true});
}
return Promise.reject(error.response);
}
}
});
积极响应截击器很好理解,是服务器返回给他们的统计数据,他们在拿到之前能对他进行一些处理。比如上面的思想:假如后台返回的状态码是200,则正常返回统计数据,否则的依照错误的状态码类别进行一些他们需要的错误,只不过这里主要就是进行了错误的标准化处理和没登录或登录过期后调整登录页的两个操作。
要注意的是,上面的Toast()方法,是我导入的vant库中的toast轻提示组件,你依照你的ui库,对应采用你的两个提示组件。
PCBget方法和post方法
他们常用的ajax允诺方法有get、post、put等方法,相信小伙伴都不会陌生。axios对应的也有许多类似的方法,不清楚的能看下文件格式。但是为了精简他们的标识符,他们还是要对其进行两个简单的PCB。下面他们主要就PCB两个方法: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组件来格式化他们的参数。这个很重要,假如没有格式化操作,后台是拿不到你提交的统计数据的。这是文章开头他们import QS from 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的第二个参数是两个参数对象。两者略微的区别要留意哦!
axios的PCB基本就完成了,下面再简单说下api的标准化管理工作。
整齐的api就像电路板一样,即使再复杂也能很清晰整个线路。上面说了,他们会增建两个api.js,接着在这个文件格式中存放他们所有的apiUSB。
首先他们在api.js中导入他们PCB的get和post方法/**
* apiUSB标准化管理工作
*/ import { get, post } from ./http
现在,比如他们有这样两个USB,是两个post允诺:
http://www.baiodu.com/api/v1/users/my_address/address_edit_before他们能在api.js中这样PCB:
export const apiAddress = p => post(api/v1/users/my_address/address_edit_before, p);
他们定义了两个apiAddress方法,这个方法有两个参数p,p是他们允诺USB时携带的参数对象。而后调用了他们PCB的post方法,post方法的第两个参数是他们的USB地址,第二个参数是apiAddress的p参数,即允诺USB时携带的参数对象。最后通过export导出apiAddress。
接着在他们的页面中能这样调用他们的apiUSB:
import { apiAddress } from @/request/api;// 导入他们的apiUSBexport default{
name: Address,
created () {
this.onLoad();
},
methods: {
onLoad() {
// 调用apiUSB,并且提供了两个参数apiAddress({
type: 0,
sort: 1 }).then(res =>{
………………
})
}
}
}
其他的apiUSB,就在pai.js中继续往下面扩展就能了。友情提示,为每个USB写好注释哦!!!
apiUSB管理工作的两个好处是,他们把api标准化集中起来,假如中后期需要修改USB,他们就间接在api.js中找到对应的修改就好了,而不用去每两个页面查找他们的USB接着再修改会很麻烦。关键是,万一修改的量比较大,就规格gg了。还有是假如间接在他们的业务标识符修改USB,一不小心还容易动到他们的业务标识符造成不必要的麻烦。
好了,最后把完成的axiosPCB标识符奉上。
/**axiosPCB
* 允诺截击、相应截击、错误标准化处理
*/ import axios from axios;import QS from qs;
import { Toast } from vant;
import store from ../store/index// 环境的切换if (process.env.NODE_ENV == development) {
axios.defaults.baseURL =/api;
} else if (process.env.NODE_ENV == debug) {
axios.defaults.baseURL = ;
} else if(process.env.NODE_ENV ==production) {
axios.defaults.baseURL = http://api.123dailu.com/;
}
// 允诺超时时间axios.defaults.timeout =10000;
// post允诺头axios.defaults.headers.post[Content-Type] = application/x-www-form-urlencoded;charset=UTF-8;
// 允诺截击器axios.interceptors.request.use(
config =>{
// 每次发送允诺之前判断是否存在token,假如存在,则标准化在http允诺的header都加上token,不用每次允诺都手动添加了 // 即使本地存在token,也有可能token是过期的,因此在积极响应截击器中要对返回状态进行判断 consttoken = store.state.token;
token && (config.headers.Authorization = token);
returnconfig;
},
error => {
return Promise.error(error);
})
// 积极响应截击器axios.interceptors.response.use(
response =>{
if (response.status === 200) {
return Promise.resolve(response);
} else{
return Promise.reject(response);
}
},
// 服务器状态码不是200的情况error => {
if(error.response.status) {
switch(error.response.status) {
// 401: 未登录 // 未登录则跳转登录页面,并携带当前页面的路径 // 在登录成功后返回当前页面,这一步需要在登录页操作。 case 401:
router.replace({
path: /login,
query: { redirect: router.currentRoute.fullPath }
});
break;
// 403 token过期 // 登录过期对用户进行提示 // 清除本地token和清空vuex中token对象 // 跳转登录页面 case 403:
Toast({
message: 登录过期,请重新登录,
duration: 1000,
forbidClick: true});
// 清除token localStorage.removeItem(token);
store.commit(loginSuccess, null);
// 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面 setTimeout(() =>{
router.replace({
path: /login,
query: {
redirect: router.currentRoute.fullPath
}
});
}, 1000);
break;
// 404允诺不存在 case 404:
Toast({
message: 网络允诺不存在,
duration: 1500,
forbidClick: true});
break;
// 其他错误,间接抛出错误提示 default:
Toast({
message: error.response.data.message,
duration: 1500,
forbidClick: true});
}
return Promise.reject(error.response);
}
}
);
/**
* 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方法,对应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)
})
});
}
假如喜欢,就给个❤❤吧(*^▽^*)
*********华丽丽的分割线******************华丽丽的分割线******************华丽丽的分割线******************华丽丽的分割线******************华丽丽的分割线*********
2018.8.14预览
axios的PCB依照需求的不同而不同。这里非常感谢评论里一些很中肯的建议,我也对此进行了思考和针对不同需求的改善。主要就有以下改变:
1.优化axiosPCB,去掉之前的get和post
2.断网情况处理
3.更加组件化的api管理工作
4.USB域名有多个的情况
5.api挂载到vue.prototype上省去导入的步骤
http.js中axiosPCB的优化,先间接贴标识符:
/**
* axiosPCB
* 允诺截击、积极响应截击、错误标准化处理
*/ 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
}
});
}
/**
* 允诺失败后的错误标准化处理
* @param {Number} status 允诺失败的状态码
*/ const errorHandle = (status, other) =>{
// 状态码判断 switch(status) {
// 401: 未登录状态,跳转登录页 case 401:
toLogin();
break;
// 403 token过期 // 清除token并跳转登录页 case 403:
tip(登录过期,请重新登录);
localStorage.removeItem(token);
store.commit(loginSuccess, null);
setTimeout(() =>{
toLogin();
}, 1000);
break;
// 404允诺不存在 case 404:
tip(允诺的资源不存在);
break;
default:
console.log(other);
}}
// 创建axios实例varinstance = axios.create({timeout: 1000 * 12});
// 设置post允诺头instance.defaults.headers.post[Content-Type] = application/x-www-form-urlencoded;
/**
* 允诺截击器
* 每次允诺前,假如存在token则在允诺头中携带token
*/instance.interceptors.request.use(
config =>{
// 登录流程控制中,依照本地是否存在token判断用户的登录情况 // 但是即使token存在,也有可能token是过期的,因此在每次的允诺头中携带token // 后台依照携带的token判断用户的登录情况,并返回给他们对应的状态码 // 而后他们能在积极响应截击器中,依照状态码进行一些标准化的操作。 consttoken = store.state.token;
token && (config.headers.Authorization = token);
returnconfig;
},
error => Promise.error(error))
// 积极响应截击器instance.interceptors.response.use(
// 允诺成功 res => res.status === 200 ? Promise.resolve(res) : 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中控制着两个全局的断网提示组件的显示隐藏 if (!window.navigator.onLine) {
store.commit(changeNetwork, false);
} else{
return Promise.reject(error);
}
}
});
export defaultinstance;
这个axios和之前的大同小异,做了如下几点改变:
1.去掉了之前get和post方法的PCB,通过创建两个axios实例接着export default方法导出,这样采用起来更灵活一些。
2.去掉了通过环境变量控制baseUrl的值。考虑到USB会有多个不同域名的情况,因此准备通过js变量来控制USB域名。这点具体在api里会介绍。
3.增加了允诺超时,即断网状态的处理。说下思路,当断网时,通过预览vuex中network的状态来控制断网提示组件的显示隐藏。断网提示通常会有重新加载统计数据的操作,这步会在前直面应的地方介绍。
4.公用函数进行抽出,精简标识符,尽量保证单一职责原则。
下面说下api这块,考虑到一下需求:
1.更加组件化
2.更方便多人开发,有效减少解决命名冲突
3.处理USB域名有多个情况
这里这里呢增建了两个api文件格式夹,里头有两个index.js和两个base.js,以及多个依照组件划分的USBjs文件。index.js是两个api的出口,base.js管理工作USB域名,其他js则用以管理工作各个组件的USB。
先放index.js标识符:
/**
* apiUSB的标准化出口
*/ // 文章组件USBimportarticlefrom @/api/article;
// 其他组件的USB……// 导出USBexport default{
article,
// ……}
index.js是两个apiUSB的出口,这样就能把apiUSB根据功能划分为多个组件,有利于多人协作开发,比如两个人只负责两个组件的开发等,还能方便每个组件中USB的命名哦。
base.js:
/**
* USB域名的管理工作
*/ constbase = {
sq: https://xxxx111111.com/api/v1,
bd: http://xxxxx22222.com/api}
export defaultbase;
通过base.js来管理工作他们的USB域名,不管有多少个都能通过这里进行USB的定义。即使修改起来,也是很方便的。
最后是USB组件的说明,比如上面的article.js:
/**
* article组件USB列表
*/ import base from ./base; // 导入USB域名列表import axios from @/utils/http; // 导入http中创建的axios实例import qs from qs; // 依照需求是否导入qs组件constarticle = {
// 新闻列表articleList () {
return axios.get(`${base.sq}/topics`);
},
// 新闻详情,演示articleDetail (id, params) {
return axios.get(`${base.sq}/topic/${id}`, {
params: params
});
},
// post提交login (params) {
returnaxios.post(`${base.sq}/accesstoken`, qs.stringify(params));
}
// 其他USB…………}
export defaultarticle;
1.通过间接导入他们PCB好的axios实例,接着定义USB、调用axios实例并返回,能更灵活的采用axios,比如你能对post允诺时提交的统计数据进行两个qs格式化的处理等。
2.允诺的配置更灵活,你能针对某个需求进行两个不同的配置。关于配置的优先级,axios文件格式说的很清楚,这个顺序是:在 lib/defaults.js 找到的库的默认值,接着是实例的 defaults 属性,最后是允诺的 config 参数。后者将优先于前者。
3.restful风格的USB,也能通过这种方式灵活的设置apiUSB地址。
最后,为了方便api的调用,他们需要将其挂载到vue的原型上。在main.js中:
import Vue from vueimport App from ./Appimport router from ./router // 导入路由文件格式import store from ./store // 导入vuex文件格式import api from ./api // 导入apiUSBVue.prototype.$api = api; // 将api挂载到vue的原型上接着他们能在页面中这样调用USB,eg:
methods: {
onLoad(id) {
this.$api.article.articleDetail(id, {
api: 123 }).then(res=>{
// 执行某些操作})
}
}
再提一下断网的处理,这里只做两个简单的示例:
<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的状态来判断是否需要加载这个断网组件。断网情况下teEnter钩子中再返回当前页面。
// refresh.vuebeforeRouteEnter (to, from, next) {
next(vm =>{
vm.$router.replace(from.fullPath)
})
}
这是一种全局通用的断网提示,当然了,也能依照他们的工程项目需求操作。具体操作就仁者见仁智者见智了。
假如更多的需求,或者说是不一样的需求,能依照他们的需求进行两个改进。
假如感觉对你有协助,那就收藏❤❤吧!
文末福利:送出一些极客时间专栏(原价129块任选一门)/红宝书/绿皮书,不过需要帮我个忙–预约一门公开课,感兴趣的小伙伴能加阿浪微信【frontJS】,截止时间至10.17日晚上21:00,名额不多!