项目中,全局loading一般是在整体内容区域进行展示,项目在调用接口的时候展示,接口调用完成后隐藏,防止在页面接口请求过程中用户进行意料之外的操作。这次就是对loading的一个处理过程
项目构成:vue2+axios+elementUI
像elementUI之类的UI框架中都会封装好了loading的样式结构之类的,如果没有用这类框架的话也可以自己封装一个loading。
最开始瞄一眼需求,开始就想当然的认为在请求拦截器中把loading设置为true,在响应拦截器中设置为false不就行了,easy~
关于请求、响应拦截器可查看官方文档
axios文档
这里稍微解释下请求响应拦截器:拦截器会在请求或响应被 then 或 catch 处理前拦截它们。
比如去吃火锅的话,羊肉就相当于我们的请求。
上面那个想当然的方法确实可以实现:不过有特定场景,比如我的页面只有一个请求,那么这个效果肯定是对的。但是一般而言(或者说绝大多数情况下),我们的页面都会有并发和继发请求的情况。所以要对这两种方式进行处理。
并发请求
请求同时发出,但是响应时间不定。例如:
created() {
axios.get()
axios.post()
axios.get()
// 或者是:
const res1Promise = xxxx()
const res2Promise = xxxx()
const res1 = await res1Promise
const res2 = await res2Promise
}
上面的这两种方式的请求都属于并发请求。
继发请求
一个请求发出且接收到响应之后再发出下一个请求,这样就是继发请求。常见的比如请求成功回调函数中继续发送请求
created() {
axios.get().then(res => {
axios.post()
})
// 或者是
const res1 = await xxxx()
const res2 = await xxxxx()
}
像上面这两种都属于继发请求。
vuex中存储一个count值,每次请求发起之前count+1 ,响应完成之后count-1,当count为0的时候证明所有请求完成了。
vuex
const state = {
requestCount: 0
}
const mutations = {
ADD_REQUEST_COUNT: (state) => {
state.requestCount = state.requestCount + 1
},
SUBTRACT_REQUEST_COUNT: (state) => {
state.requestCount = state.requestCount - 1
if (state.requestCount < 0) state.requestCount = 0
},
}
axios设置
注意这里面定义了配置过后新的axios为service,下文中也都是使用的service
const service = axios.create({....})
service.interceptors.request.use(config => {
store.commit('global/ADD_REQUEST_COUNT')
return config
}, (err) => {
store.commit('global/SUBTRACT_REQUEST_COUNT')
return Promise.resolve(err)
})
service.interceptors.response.use(data => {
store.commit('global/SUBTRACT_REQUEST_COUNT')
return data
}, err => {
store.commit('global/SUBTRACT_REQUEST_COUNT')
return Promise.reject(err)
})
继发请求是在当前某个请求完成之后紧接着发起下一个请求,如果是继发请求的话,就不能再响应完成之后立刻结束loading,要在完成之后一段时间之内没有新的请求的话才会结束loading。
同理:我们每个请求发出之前也不能立即设置loading为true,因为如果是继发请求的话loading本身就已经为true了
所以可以在响应完成之后记录一下本次响应完成的时间,然后在请求拦截器中进行时间间隔判断,时间间隔过短的话可以认为是继发请求。我设置的继发请求时间间隔设置的100ms
vuex
const state = {
prevRequsetEndTime: 0,
requestEndTimer: null,
}
const mutations = {
SET_REQUEST_END_TIMER: (state, requestEndTimer) => {
state.requestEndTimer = requestEndTimer
},
SET_PREV_REQUEST_END_TIME: (state, prevRequsetEndTime) => {
state.prevRequsetEndTime = prevRequsetEndTime
},
}
axios
const requestContinue = 100
// 请求拦截器
service.interceptors.request.use(config => {
const { requestCount, prevRequsetEndTime, requestEndTimer } = store.getters
const nowTime = +new Date()
if (nowTime - prevRequsetEndTime < requestContinue) {
clearTimeout(requestEndTimer)
} else if (requestCount === 0) {
store.commit('global/SET_LOADING', true)
}
store.commit('global/ADD_REQUEST_COUNT')
return config
}, (err) => {
store.commit('global/SUBTRACT_REQUEST_COUNT')
const { requestCount } = store.getters
if (requestCount === 0) {
const timer = setTimeout(() => {
store.commit('global/SET_LOADING', false)
}, requestContinue)
store.commit('global/SET_REQUEST_END_TIMER', timer)
}
return Promise.resolve(err)
})
// 响应拦截器
service.interceptors.response.use(data => {
store.commit('global/SUBTRACT_REQUEST_COUNT')
store.commit('global/SET_PREV_REQUEST_END_TIME', +new Date())
const { requestCount } = store.getters
if (requestCount === 0) {
const timer = setTimeout(() => {
store.commit('global/SET_LOADING', false)
}, requestContinue)
store.commit('global/SET_REQUEST_END_TIMER', timer)
}
return data.data
}, err => {
store.commit('global/SUBTRACT_REQUEST_COUNT')
const { requestCount } = store.getters
if (requestCount === 0) {
const timer = setTimeout(() => {
store.commit('global/SET_LOADING', false)
}, requestContinue)
store.commit('global/SET_REQUEST_END_TIMER', timer)
}
return Promise.reject(err)
})
项目中有某些接口是不需要全局loading的,项目中未使用websocket的话常见的比如:ajax轮询,也就是定时器每隔一段时间调用一次接口,这种情况下loading的true与false其实和这种请求是没有关联的。
所以现在需要做的是区分这种不需要loading的特殊请求和一般请求
在axios
文档中有请求配置这一项,在请求时可以配置这一项,所以可以设置不需要请求的showLoading为false,默认为true
关于这个请求配置,示例如下:
service({
method: 'get',
url,
loadingConfig: { showLoading: true }
})
// 在请求拦截器中,可以通过config.loadingConfig获取参数
service.interceptors.request.use(config => {
if(config.loadingConfig.showLoading) {
...
}
})
// 在响应拦截器中,可以通过data.config.loadingConfig.showLoading获取参数
service.interceptors.request.use(data => {
if(data.config.loadingConfig.showLoading) {
...
}
})
这样就可以通过设置config来区分哪些是需要设置全局loading的
注意:
内容区域与表格loading会有冲突
一般而言表格区域会额外加一个表格的loading,然后全局也有一个loading,可以为了防止两者冲突可以统一设置表格接口showLoading为false。
因为公司后台使用的是RESTful API
,所以在axios统一配置文件中导出了例如get
post
delete
请求等,统一配置就可以在这里进行
例如:
export const getRequest = (url, config = { showLoading: true }) => {
const params = url.split('?')[1]
if (params && params.indexOf('page') !== -1 && params.indexOf('size') !== -1) {
config = { showLoading: false }
}
return service({
method: 'get',
url,
loadingConfig: config
})
}
如果有好的实现方法还望不吝赐教