技术实践

关于keep-alive生命周期钩子

在开发Vue项目的时候,大部分组件是没必要多次渲染的,所以Vue提供了一个内置组件keep-alive来缓存组件内部状态,避免重新渲染,

羊先生的头像
羊先生2020.02.12 · 8 分钟阅读 · 32 阅读
关于keep-alive生命周期钩子封面
文章正文还需 8 分钟

在开发Vue项目的时候,大部分组件是没必要多次渲染的,所以Vue提供了一个内置组件keep-alive来缓存组件内部状态,避免重新渲染,
是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中

keep-alive

在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子:activated 与 deactivated

activated:在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用

deactivated:组件被停用(离开路由)时调用

实际列子

html 复制代码
<template>
	<div>
		<router-link to="/login" class="default-styles">Login</router-link><br> 
		<router-link :to="{ path: '/about' }" class="default-styles">About</router-link><br>
		<router-link :to="{ path: '/user'}" class="default-styles">User</router-link><br>
		<!--缓存user,login-->
		<keep-alive include="user,login">
			<router-view></router-view>
		</keep-alive>
	</div>
</template>

include:name 匹配的 路由/组件 会被缓存
exclude:name 匹配的 路由/组件 不会被缓存

exclude的优先级大于include,也就是includeexclude同时存在时,exclude生效,include不生效
includeexclude支持三种方式来有条件的缓存路由:采用逗号分隔的字符串形式,正则形式,数组形式,
正则和数组形式,必须采用v-bind形式来使用

使用了keep-alive就不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了

所以在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子:activated deactivated
activated: 在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用
deactivated: 组件被停用(离开路由)时调用

注意 这是组件的name值,不是导航的name值

main.js

js 复制代码
const router = new VueRouter({
    mode: 'hash',
    routes: [{
            path: '/user',
            component: User,
            name: 'user',
            beforeEnter: function guardRoute(to, from, next) {
                console.log('user导航路由独享守卫:beforeEnter')
                next()
            }
        },
        {
            path: '/about',
            component: About,
            name: 'about',
            beforeEnter: function guardRoute(to, from, next) {
                console.log('about导航路由独享守卫:beforeEnter')
                next()
            }
        },
        {
            path: '/login',
            component: Login,
            name: 'login',
            beforeEnter: function guardRoute(to, from, next) {
                console.log('login导航路由独享守卫:beforeEnter')
                next()
            }
        }
    ]
})
router.beforeEach((to, from, next) => {
    console.log('全局前置守卫: beforeEach')
    next()
})
router.beforeResolve((to, from, next) => {
    console.log('全局解析守卫: beforeResolve')
    next()
})
router.afterEach((to, from) => {
    console.log('全局后置钩子: afterEach')
})

Vue.use(VueRouter)
new Vue({
    router,
    render: h => h(App),
}).$mount('#app')

login组件

html 复制代码
<template>
    <div class="login">
        这是login
    </div>
</template>
<script>
export default {
    name:'login',
    beforeCreate(){
        console.log('login组件beforeCreate')
    },
    created(){
        console.log('login组件created')
    },
    beforeMount(){
        console.log('login组件beforeMount')
    },
    mounted(){
        console.log('login组件mounted')
    },
    activated(){
        console.log('login组件activated')
    },
    deactivated(){
        console.log('login组件deactivated')
    },
    beforeRouteEnter(to, from, next) {
        console.log('login组件路由内守卫进入前:beforeRouteEnter')
        next()
    },
    beforeRouteUpdate(to, from, next) {
        console.log('login组件路由内守卫路由改变时:beforeRouteUpdate')
        next()
    },
    beforeRouteLeave(to, from, next) {
        console.log('login组件路由内守卫离开前:beforeRouteLeave')
        next()
    },
    beforeDestroy(){
        console.log('login组件beforeDestroy')
    },
    destroyed(){
        console.log('login组件destroyed')
    }
}
</script>

about组件

html 复制代码
<template>
    <div class="about">
        about
    </div>
</template>
<script>
export default {
    name:'about',
    beforeCreate(){
        console.log('about组件beforeCreate')
    },
    created(){
        console.log('about组件created')
    },
    beforeMount(){
        console.log('about组件beforeMount')
    },
    mounted(){
        console.log('about组件mounted')
    },
    activated(){
        console.log('about组件activated')
    },
    deactivated(){
        console.log('about组件deactivated')
    },
    beforeRouteEnter(to, from, next) {
        console.log('about组件路由内守卫进入前:beforeRouteEnter')
        next()
    },
    beforeRouteUpdate(to, from, next) {
        console.log('about组件路由内守卫路由改变时:beforeRouteUpdate')
        next()
    },
    beforeRouteLeave(to, from, next) {
        console.log('about组件路由内守卫离开前:beforeRouteLeave')
        next()
    },
    beforeDestroy(){
        console.log('about组件beforeDestroy')
    },
    destroyed(){
        console.log('about组件destroyed')
    }
}
</script>
<style lang="less" scoped>

</style>

user组件

html 复制代码
<template>
    <div class="user">
        user
    </div>
</template>
<script>
export default {
    name:'user',
    beforeCreate(){
        console.log('user组件beforeCreate')
    },
    created(){
        console.log('user组件created')
    },
    beforeMount(){
        console.log('user组件beforeMount')
    },
    mounted(){
        console.log('user组件mounted')
    },
    activated(){
        console.log('user组件activated')
    },
    deactivated(){
        console.log('user组件deactivated')
    },
    beforeRouteEnter(to, from, next) {
        console.log('user组件路由内守卫进入前:beforeRouteEnter')
        next()
    },
    beforeRouteUpdate(to, from, next) {
        console.log('user组件路由内守卫路由改变时:beforeRouteUpdate')
        next()
    },
    beforeRouteLeave(to, from, next) {
        console.log('user组件路由内守卫离开前:beforeRouteLeave')
        next()
    },
    beforeDestroy(){
        console.log('user组件beforeDestroy')
    },
    destroyed(){
        console.log('user组件destroyed')
    }
}
</script>

执行顺序

刷新浏览器,在这里我不分析其他钩子函数,如需要了解其他钩子函数可以看《你在使用vue-router时可能需要注意到的事项》这篇文章
1、然后点击第一次进入login组件,可以看到login组件执行了activated函数,说明组件被缓存

image.png

2、当从login组件跳转到about组件(该组件不是缓存组件),可以看到login组件执行了deactivated函数,说明组件被停用了

image.png

3、从about组件跳转到user组件,发现about执行了beforeDestroydestroyed销毁前后函数
user是缓存组件,并且也执行了activated函数,组件已被缓存

image.png

4、从user组件跳转到login组件,发现login的 beforeCreate created beforeMount mounted 钩子函数均为执行,但是执行了login组件的activated函数

所以你要是把数据请求挂载放到了created beforeMount mounted函数中是不会执行的,如果你即想缓存组件,又想调用某个接口,可以把ajax放在activated

image.png

总结

将使用vue-router、keep-alive、和组件生命周期钩子结合起来的页面,比如A组件跳转到B组件的一个完整的执行过程

1、beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
2、beforeEach: 路由全局前置守卫,可用于登录验证、全局路由loading等。
3、beforeEnter: 导航路由独享守卫
4、beforeRouteEnter: 路由组件的组件进入路由前钩子。
5、beforeResolve:路由全局解析守卫
6、afterEach:路由全局后置钩子
7、beforeCreate:组件生命周期,不能访问this。
8、created:组件生命周期,可以访问this,不能访问dom。
9、beforeMount:组件生命周期,生成html
10、deactivated: 离开缓存组件A,没有缓存的话者触发A的beforeDestroydestroyed组件销毁钩子。
11、mounted:访问/操作DOM。
12、activated:进入缓存组件,如果A中含有子组件会执行,会先执行子组件的activated,在执行父组件的activated