LazyLoadView 导致组件内导航守卫失效问题解决方案

为了在异步组件加载过程中显示加载中状态,引入了 LazyLoadView,但这种方案直接导致了组件内的导航守卫 beforeRouteEnter/beforeRouteLeave 失效,下面就是该问题的一种尝试解决方案:

创建 eventbus

1
2
3
4
5
6
// src/utils/eventbus.js
import Vue from 'vue'

const eventbus = new Vue()

export default eventbus

为了方便使用,我们将 eventbus 注册在 Vue 的原型上

1
2
3
4
// src/main.js
import Vue from 'vue'
import eventbus from '@/utils/eventbus'
Vue.prototype.$eventbus = eventbus

自定义导航守卫(也就是自定义事件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// src/routeGuard.js

/**
* 由于 lazy-load-view 的影响导致组件内导航守卫不触发,在此通过 eventbus 实现自定义导航守卫
* @author shawchen08
*/

import eventbus from '@/utils/eventbus'

// Note: 在需要监听 beforeRouteEnter 的组件中,需要在 created 中执行
// eventbus.$emit(`before${route}Enter`, to, from) 原因在于 afterEach 先于组件的生命周期钩子
// 执行,那么首次进入就不会未执行 beforeRouteEnter 的回调逻辑,因此需手动触发一次

// 需要 beforeRouteEnter 的路由
const routeEnterList = ['RouteA', 'RouteB']
const beforeRouteEnter = {}

routeEnterList.forEach(route => {
beforeRouteEnter[route] = (to, from) => {
eventbus.$emit(`before${route}Enter`, to, from)
}
})

// 需要 beforeRouteLeave 的路由
const routeLeaveList = ['RouteC', 'RouteD', 'RouteE']
const beforeRouteLeave = {}

routeLeaveList.forEach(route => {
beforeRouteLeave[route] = (to, from, next) => {
eventbus.$emit(`before${route}Leave`, to, from, next)
}
})

export { beforeRouteEnter, beforeRouteLeave }

我们把需要使用组件内导航守卫 beforeRouteEnterbeforeRouteLeave 的组件名(路由名与组件名一致可以通过此脚本检测)分别添加到上面文件对应的数组 routeEnterListrouteLeaveList

通过全局守卫触发事件,模拟组件内导航守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/permission.js
import Vue from 'vue'
import router from './router'
import { beforeRouteEnter, beforeRouteLeave } from '@/routeGuard'
// ...

// ! Note: 由于 beforeEach 中有很多权限控制逻辑,
// ! 为了简单处理,我们在 beforeResolve 触发自定义 beforeRouteLeave 事件
router.beforeResolve((to, from, next) => {
if (beforeRouteLeave[from.name]) {
return beforeRouteLeave[from.name](to, from, next)
}
next()
})

router.afterEach((to, from) => {
// 记录 to 和 from,组件内需要使用
Vue.prototype.$beforeRouteEnterParams = { to, from }
if (beforeRouteEnter[to.name]) {
beforeRouteEnter[to.name](to, from)
}
// ...
})

组件内部监听相应的事件,模拟组件内导航守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<script>
// ...

export default {
// ...
created() {
// ! 先存储当前路由名(beforeDestroy 钩子中 this.$route 可能已经变了)
this._name = this.$route.name

// 这些不放在 beforeCreate 中的原因在于 beforeCreate 中还拿不到 methods 中的方法
// 类似 addEventlistioner 和 removeEventListioner,on 和 off 的回调函数必须是同一个
this.$eventbus.$off(`before${this._name}Enter`, this.beforeRouteEnter)
this.$eventbus.$on(`before${this._name}Enter`, this.beforeRouteEnter)
this.$eventbus.$off(`before${this._name}Leave`, this.beforeRouteLeave)
this.$eventbus.$on(`before${this._name}Leave`, this.beforeRouteLeave)

// 由于全局守卫 afterEach 会先于组件的 created 执行,导致第一次未触发
// `before${this.$route.name}Enter` 事件,这里手动触发一下,
// this.$beforeRouteEnterParams 是在全局守卫 afterEach 中记录的 to 和 from
// ! 注意:this.beforeRouteEnter 中有依赖数据初始化的,初始化逻辑需在此之前
this.$eventbus.$emit(`before${this._name}Enter`, this.$beforeRouteEnterParams.to, this.$beforeRouteEnterParams.from)
// ...
},
beforeDestroy() {
// ! 这里不用 this.$route.name 是因为销毁前路由可能已经改变
this.$eventbus.$off(`before${this._name}Enter`, this.beforeRouteEnter)
this.$eventbus.$off(`before${this._name}Leave`, this.beforeRouteLeave)
// ...
},
methods: {
// 模拟的 beforeRouteEnter
beforeRouteEnter(to, from) {
// ...
},
// 模拟的 beforeRouteLeave
beforeRouteLeave(to, from, next) {
// ...
// 记得执行 next()
}
}
// ...
}
</script>