详解Vue的钩⼦函数(路由导航守卫、keep-alive、⽣命周期钩
⼦)
详解Vue的钩⼦函数(路由导航守卫、keep-alive、⽣命周期钩⼦)
说到Vue的钩⼦函数,可能很多⼈只停留在⼀些很简单常⽤的钩⼦(created,mounted),⽽且对于⾥⾯的区别,什么时候该⽤什么钩⼦,并没有仔细的去研究过,且Vue的⽣命周期在⾯试中也算是⽐较⾼频的考点,那么该如何回答这类问题,让⼈有眼前⼀亮的感觉呢…
Vue-Router导航守卫:
有的时候,我们需要通过路由来进⾏⼀些操作,⽐如最常见的登录权限验证,当⽤户满⾜条件时,才让其进⼊导航,否则就取消跳转,并跳到登录页⾯让其登录。
为此我们有很多种⽅法可以植⼊路由的导航过程:全局的, 单个路由独享的, 或者组件级的,推荐优先阅读路由⽂档
全局守卫
vue-router全局有三个守卫:
  router.beforeEach 全局前置守卫进⼊路由之前
  router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调⽤之后调⽤
  router.afterEach 全局后置钩⼦进⼊路由之后
  使⽤⽅法:
  // main.js ⼊⼝⽂件
import router from ‘./router’; // 引⼊路由
router.beforeEach((to, from, next) => {
next();
});
router.beforeResolve((to, from, next) => {
next();
});
router.afterEach((to, from) => {
console.log(‘afterEach 全局后置钩⼦’);
});
to,from,next 这三个参数:
ajax实例里面的函数  to和from是将要进⼊和将要离开的路由对象,路由对象指的是平时通过this.$route获取到的路由对象。
  next:Function 这个参数是个函数,且必须调⽤,否则不能进⼊路由(页⾯空⽩)。
  next() 进⼊该路由。
  next(false): 取消进⼊路由,url地址重置为from路由地址(也就是将要离开的路由地址)。
  next 跳转新路由,当前的导航被中断,重新开始⼀个新的导航。
我们可以这样跳转:next(‘path地址’)或者next({path:’’})或者next({name:’’})
且允许设置诸如 replace: true、name: ‘home’ 之类的选项
以及你⽤在router-link或router.push的对象选项。
路由独享守卫
如果你不想全局配置守卫的话,你可以为某些路由单独配置守卫:
const router = new VueRouter({
routes: [
{
path: ‘/foo’,
component: Foo,
beforeEnter: (to, from, next) => {
// 参数⽤法什么的都⼀样,调⽤顺序在全局前置守卫后⾯,所以不会被全局守卫覆盖
/
/ …
}
}
]
})
路由组件内的守卫:
  beforeRouteEnter 进⼊路由前
  beforeRouteUpdate (2.2) 路由复⽤同⼀个组件时
  beforeRouteLeave 离开当前路由时
⽂档中的介绍:
beforeRouteEnter (to, from, next) {
/
/ 在路由独享守卫后调⽤不!能!获取组件实例 this,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复⽤时调⽤可以访问组件实例 this
// 举例来说,对于⼀个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复⽤。⽽这个钩⼦就会在这个情况下被调⽤。
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调⽤,可以访问组件实例 this
}
beforeRouteEnter访问this
因为钩⼦在组件实例还没被创建的时候调⽤,所以不能获取组件实例 this,可以通过传⼀个回调给next来访问组件实例。
但是回调的执⾏时机在mounted后⾯,所以在我看来这⾥对this的访问意义不太⼤,可以放在created或者mounted⾥⾯。
beforeRouteEnter (to, from, next) {
console.log(‘在路由独享守卫后调⽤’);
next(vm => {
// 通过 vm 访问组件实例this 执⾏回调的时机在mounted后⾯,
})
}
beforeRouteLeave:
导航离开该组件的对应路由时调⽤,我们⽤它来禁⽌⽤户离开,⽐如还未保存草稿,或者在⽤户离开前,
将setInterval销毁,防⽌离开之后,定时器还在调⽤。beforeRouteLeave (to, from , next) {
if (⽂章保存) {
next(); // 允许离开或者可以跳到别的路由上⾯讲过了
} else {
next(false); // 取消离开
}
}
关于钩⼦的⼀些知识:
路由钩⼦函数的错误捕获
如果我们在全局守卫/路由独享守卫/组件路由守卫的钩⼦函数中有错误,可以这样捕获:
/
/ 2.4.0新增并不常⽤,了解⼀下就可以了
console.log(callback, ‘callback’);
});
在路由⽂档中还有更多的实例⽅法:动态添加路由等,有兴趣可以了解⼀下。
跳转死循环,页⾯永远空⽩
我了解到的,很多⼈会碰到这个问题,来看⼀下这段伪代码:
router.beforeEach((to, from, next) => {
if(登录){
next()
}else{
next({ name: ‘login’ });
}
});
看逻辑貌似是对的,但是当我们跳转到login之后,因为此时还是未登录状态,所以会⼀直跳转到login然后死循环,页⾯⼀直是空⽩的,所以:我们需要把判断条件稍微改⼀下。
if(登录 || to.name === ‘login’){ next() }
// 登录,或者将要前往login页⾯的时候,就允许进⼊路由
全局后置钩⼦的跳转:
⽂档中提到因为router.afterEach不接受next函数所以也不会改变导航本⾝,意思就是只能当成⼀个钩⼦来使⽤,但是我⾃⼰在试的时候发现,我们可以通过这种形式来实现跳转:// main.js ⼊⼝⽂件
import router from ‘./router’; // 引⼊路由
router.afterEach((to, from) => {
if (未登录 && to.name !== ‘login’) {
router.push({ name: ‘login’ }); // 跳转login
}
});
额,通过router.beforeEach 也完全可以实现且更好,我就骚⼀下。
完整的路由导航解析流程(不包括其他⽣命周期):
触发进⼊其他路由。
调⽤要离开路由的组件守卫beforeRouteLeave
调⽤局前置守卫:beforeEach
在重⽤的组件⾥调⽤ beforeRouteUpdate
调⽤路由独享守卫 beforeEnter。
解析异步路由组件。
在将要进⼊的路由组件中调⽤beforeRouteEnter
调⽤全局解析守卫 beforeResolve
导航被确认。
调⽤全局后置钩⼦的 afterEach 钩⼦。
触发DOM更新(mounted)。
执⾏beforeRouteEnter 守卫中传给 next 的回调函数
你不知道的keep-alive[我猜你不知道]
在开发Vue项⽬的时候,⼤部分组件是没必要多次渲染的,所以Vue提供了⼀个内置组件keep-alive来缓存组件内部状态,避免重新渲染,⽂档在这⾥。
⽂档:和相似,是⼀个抽象组件:它⾃⾝不会渲染⼀个 DOM 元素,也不会出现在⽗组件链中。
⽤法:
缓存动态组件:
包裹动态组件时,会缓存不活动的组件实例,⽽不是销毁它们,此种⽅式并⽆太⼤的实⽤意义。
缓存路由组件:使⽤keep-alive可以将所有路径匹配到的路由组件都缓存起来,包括路由组件⾥⾯的组件,keep-alive⼤多数使⽤场景就是这种。⽣命周期钩⼦:这篇既然是Vue钩⼦函数的专场,那肯定要扣题呀~ 在被keep-alive包含的组件/路由中,会多出两个⽣命周期的钩
⼦:activated 与 deactivated。
⽂档:在 2.2.0 及其更⾼版本中,activated 和 deactivated 将会在树内的所有嵌套组件中触发。
activated在组件第⼀次渲染时会被调⽤,之后在每次缓存组件被激活时调⽤。
activated调⽤时机:
第⼀次进⼊缓存路由/组件,在mounted后⾯,beforeRouteEnter守卫传给 next 的回调函数之前调⽤:
beforeMount=> 如果你是从别的路由/组件进来(组件销毁destroyed/或离开缓存deactivated)=>mounted=> activated 进⼊缓存组件 => 执⾏beforeRouteEnter回调
因为组件被缓存了,再次进⼊缓存路由/组件时,不会触发这些钩⼦:
/
/ beforeCreate created beforeMount mounted 都不会触发。
所以之后的调⽤时机是:
组件销毁destroyed/或离开缓存deactivated => activated 进⼊当前缓存组件 => 执⾏ beforeRouteEnter回调
// 组件缓存或销毁,嵌套组件的销毁和缓存也在这⾥触发
deactivated:组件被停⽤(离开路由)时调⽤
使⽤了keep-alive就不会调⽤beforeDestroy(组件销毁前钩⼦)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了。
这个钩⼦可以看作beforeDestroy的替代,如果你缓存了组件,要在组件销毁的的时候做⼀些事情,你可以放在这个钩⼦⾥。
如果你离开了路由,会依次触发:
组件内的离开当前路由钩⼦beforeRouteLeave => 路由前置守卫 beforeEach =>全局后置钩⼦afterEach => deactivated 离开缓存组件 => activated 进⼊缓存组件(如果你进⼊的也是缓存路由)
如果离开的组件没有缓存的话 beforeDestroy会替换deactivated
如果进⼊的路由也没有缓存的话全局后置钩⼦afterEach=>销毁 destroyed=> beforeCreate等
那么,如果我只是想缓存其中⼏个路由/组件,那该怎么做?
缓存你想缓存的路由:
Vue2.1.0之前:
想实现类似的操作,你可以:
配置⼀下路由元信息
创建两个keep-alive标签
使⽤v-if通过路由元信息判断缓存哪些路由。
//router配置 new Router({ routes: [ { path: '/', name: 'home', component: Home, meta: { keepAlive: true // 需要被缓存 } }, { path: '/:id', name: 'edit', component: Edit, meta: { keepAlive: false // 不需要被缓存 } } ] }); Vue2.1.0版本之后:使⽤路由元信息的⽅式,要多创建⼀个router-view标签,并且每个路由都要配
置⼀个元信息,是可以实现我们想要的效果,但是过于繁琐了点。幸运的是在Vue2.1.0之后,Vue新增了两个属性配合keep-alive来有条件地缓存路由/组件。新增属性:
include:匹配的路由/组件会被缓存
exclude:匹配的路由/组件不会被缓存
include和exclude⽀持三种⽅式来有条件的缓存路由:采⽤逗号分隔的字符串形式,正则形式,数组形式。
正则和数组形式,必须采⽤v-bind形式来使⽤。
缓存组件的使⽤⽅式:
但更多场景中,我们会使⽤keep-alive来缓存路由:匹配规则:
1、⾸先匹配组件的name选项,如果name选项不可⽤。
2、则匹配它的局部注册名称。 (⽗组件 components 选项的键值)
3、匿名组件,不可匹配。
⽐如路由组件没有name选项,并且没有注册的组件名。
4、只能匹配当前被包裹的组件,不能匹配更下⾯嵌套的⼦组件。
⽐如⽤在路由上,只能匹配路由组件的name选项,不能匹配路由组件⾥⾯的嵌套组件的name选项。
5、⽂档:不会在函数式组件中正常⼯作,因为它们没有缓存实例。
6、exclude的优先级⼤于include
也就是说:当include和exclude同时存在时,exclude⽣效,include不⽣效。
当组件被exclude匹配,该组件将不会被缓存,不会调⽤activated 和 deactivated。
组件⽣命周期钩⼦:
ajax请求最好放在created⾥⾯,因为此时已经可以访问this了,请求到数据就可以直接放在data⾥⾯。这⾥也碰到过⼏次,⾯试官问:ajax请求应该放在哪个⽣命周期。
关于dom的操作要放在mounted⾥⾯,在mounted前⾯访问dom会是undefined。
每次进⼊/离开组件都要做⼀些事情,⽤什么钩⼦:
不缓存:
进⼊的时候可以⽤created和mounted钩⼦,离开的时候⽤beforeDestory和destroyed钩⼦,beforeDestory可以访问this,destroyed不可以访问this。
缓存了组件:
缓存了组件之后,再次进⼊组件不会触发beforeCreate、created 、beforeMount、 mounted,如果你想每次进⼊组件都做⼀些事情的话,你可以放在activated进⼊缓存组件的钩⼦中。
同理:离开缓存组件的时候,beforeDestroy和destroyed并不会触发,可以使⽤deactivated离开缓存组件的钩⼦来代替。
触发钩⼦的完整顺序:
将路由导航、keep-alive、和组件⽣命周期钩⼦结合起来的,触发顺序,假设是从a组件离开,第⼀次进⼊b组件:
beforeRouteLeave:路由组件的组件离开路由前钩⼦,可取消路由离开。
beforeEach: 路由全局前置守卫,可⽤于登录验证、全局路由loading等。
beforeEnter: 路由独享守卫
beforeRouteEnter: 路由组件的组件进⼊路由前钩⼦。
beforeResolve:路由全局解析守卫
afterEach:路由全局后置钩⼦
beforeCreate:组件⽣命周期,不能访问this。
created:组件⽣命周期,可以访问this,不能访问dom。
beforeMount:组件⽣命周期
deactivated: 离开缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩⼦。
mounted:访问/操作dom。
activated:进⼊缓存组件,进⼊a的嵌套⼦组件(如果有的话)。
执⾏beforeRouteEnter回调函数next。
结语
Vue提供了很多钩⼦,但很多钩⼦我们⼏乎不会⽤到,只有清楚这些钩⼦函数的触发顺序以及背后的⼀些限制等,这样我们才能够正确的使⽤这些钩⼦,希望看了本⽂的同学,能对这些钩⼦有更加清晰的认识,使⽤起来更加得⼼应⼿。