keep-alive介绍
- Vue的抽象组件,自身不会渲染一个DOM元素,也不会出现在父组件链中,能将组件在切换过程中将状态保存在内存中,防止重复渲染DOM
- 包裹动态组建时,会缓存不活动的组件实例,而不是销毁它们
- 当组件在 keep-alive内被切换时,它的activated和deactivated这两个生命周期钩子函数将会被执行
Props
- include - 字符串或正则表达式。只有名称匹配的组件会被缓存
- exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
- max - 数字。最多可以缓存多少组件实例
匹配规则
匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配,这里匿名组件暂时还没有具体搞懂,希望有大神路过此地时指点一二。
注意
- include 和 exclude 属性允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示
- exclude优先级大于include
- max:最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉
- keep-alive 要求被切换到的组件都有自己的名字,不论是通过组件的 name 选项还是局部/全局注册
字符串和数组表示的区别:
- 当:include=""为空字符串时,代表被keep-alive包裹的组件都缓存
- 当:include=[]为空数组时,代表不缓存被keep-alive包裹的组件
<keep-alive>嵌套<keep-alive>对max的影响
- 外层<keep-alive>中设置的max只对它包裹的组件有影响
- 外层嵌套的内层<keep-alive>内的缓存组件数量不会加到外层<keep-alive>的max上,
- 动态删除外层缓存的某组件,则其内层<keep-alive>缓存的组件也会一并删除
keep-alive包裹的组件多出的钩子函数
- activated:组件激活时调用,在组件第一次渲染时也会被调用,之后每次keep-alive激活时被调用
- deactivated:组件停用时调用
说明
- 当组件第一次渲染时activated也会被调用,即beforeCreate -> created -> beforeMount -> mounted -> activated
- 当跳转另一组件时即当前组件被停用时,则只会调用deactivated,它的beforeDestroy和destroy不会被调用
- 当再次激活此组件时,则也只会调用activated,其它钩子函数也不会调用
注意
- 只有组件被keep-alive包裹时,这两个生命周期函数才会被调用,正常组件不会调用这两个函数
- 使用exclude排除之后的组件,就算包裹在keep-alive中,这两个函数也不会被调用
- 在服务器端渲染期间不被调用
注意
下面的 思路、实例演示、代码示例 都是基于 动态组件 来实现的,如果Tab切换想通过嵌套路由实现,可参考这篇文章-vue实现Tab切换功能的几种方式
什么是动态组件
让多个组件使用同一个挂载点,并动态切换
component组件和keep-alive一样都是Vue的内置组件,在component里通过v-bind:is来决定哪个组件被渲染,从而实现动态组件切换效果,如
<component :is="componentId"></component>
在做Tab切换时,可通过动态组件来实现,如果用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
动态组件传值
<component :is="currentTabComponent" :componentTabName='componentTabName'/>
componentTabName:为需要传递的数据,名称可自定义
在各组件中通过prop获取即可
props:{
componentTabName:String
}
需求
在项目中经常会有 页面A -> 页面B -> 页面C 则从 页面C 返回 页面B 时 页面B 使用缓存数据,而页面A 跳到 页面B 时,则页面B每次都请求最新数据。
比如我们在某APP内点击 最新新闻(页面A) 选项 跳转到 新闻列表(页面B) 选择某一条新闻 跳转到 新闻详情(页面C) 页面,我们希望,从新闻详情返回到新闻列表时,直接用刚才请求的数据,而不每次都重新发送请求,而从 最新新闻 跳转到 新闻列表时,则都请求最新的数据
思路
- 通过使用keep-alive的include属性有条件的缓存组件
- 通过store响应式的修改include属性对应的值
- 通过组件内导航钩子beforeRouteEnter、beforeRouteLeave给store提交mutations修改
实例演示
代码示例
1:在App.vue组件通过computed计算属性响应式的获取store里的keepAliveArr计算属性,并赋值给keep-alive的include属性,并设置最多可缓存5个组件
<template>
<div id="app">
<keep-alive :include="keepAliveArr" :max="5">
<router-view></router-view>
</keep-alive>
</div>
</template>
<script>
export default {
computed: {
keepAliveArr() {
return this.$store.getters.keepAliveArr
}
}
}
2:在store的mutations中提供状态更改的方法,并通过store的计算属性供外部访问
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
//缓存组件数组
keepAliveArr: []
},
mutations: {
UPDATE_KEEP_ALIVE(state, payload) {
//当payload.type不为空则代表清除指定缓存组件,否则添加指定组件
if (payload.type) {
let index = state.keepAliveArr.indexOf(payload.keepAlive);
if (index !== -1) {
state.keepAliveArr.splice(index, 1); //删除数组的缓存的组件
}
} else {
let index = state.keepAliveArr.indexOf(payload.keepAlive);
if (index === -1) {
state.keepAliveArr.push(payload.keepAlive); //添加需要缓存的组件
}
}
}
},
getters: {
keepAliveArr: state => state.keepAliveArr
}
})
3:在组件内通过导航钩子beforeRouteEnter、beforeRouteLeave给store提交mutationsmutations修改缓存组件keepAliveArr的值,
这里page1为goods的上一个页面,page2为下一个页面,通过beforeRouteEnter钩子,不管从哪个页面进入提交commit,缓存当前页面,当离开时判断,如果是
返回上一个页面则删除当前页面缓存,否则不处理。
beforeRouteEnter(to, from, next) {
next(vm => { //添加组件缓存
vm.$store.commit("UPDATE_KEEP_ALIVE", {
keepAlive: 'goods'
})
})
},
beforeRouteLeave(to, from, next) {
if (to.path === '/page1') {//删除缓存
this.$store.commit("UPDATE_KEEP_ALIVE", {
type: 1,
keepAlive: 'goods'
})
}
next()
},
总结
通过上面三步就可以实现我们的需求,但项目中我们也经常会用到Tab切换,当Tab切换时,我们也希望缓存加载过的组件,就好比我们的需求,如果在新闻列表内加了Tab切换,
如新闻又分 科技、娱乐、财经等Tab,这个时候就是keep-alive嵌套keep-alive了,需要注意的在上面我也都说了,大家注意下就行,只是Tab切换这里我使用的动态组件。
完整代码
goods代码
<template>
<div id="dynamic-component-demo">
<div class="btn" @click="btnJumpClick">跳转到page2</div>
<nav class="tab-root">
<span v-for="(tab,index) in tabs"
:key="tab"
:class="['tab-button', { active: currentTab == index }]"
@click='currentTab = index'>
{{ tab }}
</span>
</nav>
<!--缓存各个组件-->
<keep-alive :include="cached" :max="3">
<component :is="currentTabComponent"/>
</keep-alive>
</div>
</template>
<script>
import tabGoodsRecommend from './tab-goods-recommend';
import tabGoodsDetail from './tab-goods-detail';
import tabGoodsComment from './tab-goods-comment';
export default {
name: "goods",
components: {tabGoodsRecommend, tabGoodsDetail, tabGoodsComment},
data() {
return {
cached: 'tab-goods-recommend,tab-goods-detail,tab-goods-comment',
currentTab: 0,
tabs: ['推荐', '详情', '评价']
}
},
computed: {
currentTabComponent() {
switch (this.currentTab) {
case 0:
return 'tab-goods-recommend';
case 1:
return 'tab-goods-detail';
case 2:
return 'tab-goods-comment';
}
}
},
activated() {
console.log("--demo--activated--");
},
deactivated() {
console.log("--demo--deactivated--");
},
beforeRouteEnter(to, from, next) {
next(vm => { //添加组件缓存
vm.$store.commit("UPDATE_KEEP_ALIVE", {
keepAlive: 'goods'
})
})
},
beforeRouteLeave(to, from, next) {
if (to.path === '/page1') {//删除缓存
this.$store.commit("UPDATE_KEEP_ALIVE", {
type: 1,
keepAlive: 'goods'
})
}
next()
},
methods: {
btnJumpClick() {
this.$router.push({
path: '/page2'
})
},
}
}
</script>
<style scoped lang="scss">
.btn {
width: 100%;
height: 40px;
background: #f00;
font-size: 20px;
color: white;
}
.tab-root {
display: flex;
border-bottom: 1px solid #eee;
}
.tab-button {
background: #fff;
line-height: 40px;
height: 40px;
text-align: center;
flex: 1;
font-size: 15px;
font-weight: normal;
}
.tab-button.active {
font-size: 17px;
font-weight: 500;
border-bottom: 2px solid #f00;
}
</style>
tab-goods-recommend代码
<template>
<div class="recommends-tab">
<ul class="recommends-sidebar">
<li v-for="recommend in recommends"
:key="recommend.id"
:class="{ selected: recommend === selectedRecommend }"
@click="selectedRecommend = recommend">
{{ recommend.title }}
</li>
</ul>
<div class="selected-recommend-container">
<div class="selected-recommend">
<div v-html="selectedRecommend.content"></div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "tab-goods-recommend",
data() {
return {
recommends: [{
id: 1,
title: '母婴',
content: '<p>儿童玩具、尿裤湿巾、奶粉辅食</p>'
}, {
id: 2,
title: '鞋包',
content: '<p>功能箱包、人气热卖、服饰配件</p>'
}, {
id: 3,
title: '水果',
content: '<p>瓜果桃李、海鲜水产、熟食凉菜</p>'
}
],
selectedRecommend: {}
}
},
created() {
this.selectedRecommend = this.recommends[0];
},
}
</script>
<style scoped lang="scss">
.recommends-tab {
display: flex;
}
.recommends-sidebar {
width: 20%;
text-align: center;
background: #eee;
height: 100vh;
}
.recommends-sidebar li {
height: 30px;
line-height: 30px;
}
.recommends-sidebar li.selected {
background: #fff;
color: red;
}
.selected-recommend-container {
padding-left: 10px;
}
</style>
tab-goods-detail代码
<template>
<div>商品评论</div>
</template>
<script>
export default {
name: "tab-goods-detail",
}
</script>
tab-goods-comment代码
<template>
<div>商品详情</div>
</template>
<script>
export default {
name: "tab-goods-comment",
}
</script>
上面的代码应该已经够用,如果需要全部详细代码的就留言吧,我再单独发你