问题分析
最近有个需求,需要维护一个状态,这个状态需要在多个地方访问。我一顿操作猛如虎,将这个状态使用vuex管理, 为了让这个状态可以在组件渲染之前加载,我的操作如下:
js
// 在APP.vue上调用distpatch去加载用户信息
export default{
async created(){
await this.$store.dispatch("user/getUserInfo")
}
}
在这里,我很细节 的在created前面加了async ,并且await dispatch执行结束。在我印象中,父组件的created会先于子组件的created,那我在APP阻塞住,那就会等待加载好用户信息,然后再开始执行子组件的created。
在我自信满满的提交了代码后,我亲爱的测试给我提了个bug。但是这个bug,我一直都无法复现,在一番友好交流下,发现小可爱居然打开了网络节流。
此后,我发现一个天大的秘密,vue在created上加async和await,并不会阻塞子组件的created,vue调用生命周期hook的方法如下,可以看到是同步的:
js
export function callHook( vm: Component, hook: string, args?: any[], setContext = true ) {
pushTarget()
const prevInst = currentInstance
const prevScope = getCurrentScope()
setContext && setCurrentInstance(vm)
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, args || null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
if (setContext) {
setCurrentInstance(prevInst) prevScope && prevScope.on()
}
popTarget()
}
解决方案
- 最简单的方法当然是从全局阻塞入手了,app的created入手不行,那我从main.js入手就可以了吧:
js
async function initApp(){
await this.$store.dispatch("user/getUserInfo")
return new Vue({
el: "#app",
render: h=>h(APP)
})
}
- 上述方法简单粗暴,但是未登录或token过期时,请求会被拦截,那获取数据将会失败。所以只能麻烦一点,在需要使用的组件里,先调用
await this.$store.dispatch("user/getUserInfo")
,然后再去执行页面相应逻辑,为此需要优化下getUserInfo方法:
js
const EVENT_QUEUE = []
{
actions:{
async getUserInfo({commit,state}){
// 加个字段判断是否加载完数据
if(state.isLoaded) retrun
// 当前正在加载,等待加载完成
else if(state.isLoading){
retrun new Promise(resolve=>{
EVENT_QUEUE.push(resolve)
})
}
commit("UPDATE_ISLOADING", true)
const userInfo = await fetchUserInfo()
commit("UPDATE_ISLOADING", false)
commit("UPDATE_ISLOADED", true)
commit("UPDATE_USERINFO", userInfo)
// 清空dispatch列表
while( EVENT_QUEUE.length > 0 ){
EVENT_QUEUE.pop().resolve()
}
}
}
}