<一> keepAlive的基礎知識點
<1>在動態組件上使用 keep-alive
如果希望那些標簽的組件實例能夠被在它們第一次被創建的時候緩存下來。為了解決這個問題,我們可以用一個 <keep-alive>
元素將其動態組件包裹起來。
<!-- 失活的組件將會被緩存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
具體看vue文檔中關於 keep alive部分
注意這個 <keep-alive>
要求被切換到的組件都有自己的名字,不論是通過組件的 name
選項還是局部/全局注冊。
<2>關於 keep-alive
2.1.0 新增 include and exclude
include是需要緩存的組件;
exclude是除了某些組件都緩存;
include 和 exclude prop 允許組件有條件地緩存。二者都可以用逗號分隔字符串、正則表達式或一個數組來表示:
<!-- 逗號分隔字符串 -->
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- 正則表達式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- 數組 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
匹配首先檢查組件自身的 name 選項,如果 name 選項不可用,則匹配它的局部注冊名稱 (父組件 components 選項的鍵值)。匿名組件不能被匹配。
2.5.0 新增max
最多可以緩存多少組件實例。一旦這個數字達到了,在新實例被創建之前,已緩存組件中最久沒有被訪問的實例會被銷毀掉。
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive>
<keep-alive> 不會在函數式組件中正常工作,因為它們沒有緩存實例。
(1)Props:
include - 字符串或正則表達式。只有名稱匹配的組件會被緩存。
exclude - 字符串或正則表達式。任何名稱匹配的組件都不會被緩存。
max - 數字。最多可以緩存多少組件實例。
(2)用法:
<keep-alive> 包裹動態組件時,會緩存不活動的組件實例,而不是銷毀它們。和 <transition> 相似,<keep-alive> 是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出現在組件的父組件鏈中。
當組件在 <keep-alive> 內被切換,它的 activated 和 deactivated 這兩個生命周期鈎子函數將會被對應執行。
在 2.2.0 及其更高版本中,activated 和 deactivated 將會在 <keep-alive> 樹內的所有嵌套組件中觸發。
(3)keep-alive主要用於保留組件狀態或避免重新渲染
<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<!-- 多個條件判斷的子組件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>
<!-- 和 `<transition>` 一起使用 -->
<transition>
<keep-alive>
<component :is="view"></component>
</keep-alive>
</transition>
注意,<keep-alive> 是用在其一個直屬的子組件被開關的情形。如果你在其中有 v-for 則不會工作。如果有上述的多個條件性的子元素,<keep-alive> 要求同時只有一個子元素被渲染。
<3>關於activated 與 deactivated兩個鈎子
簡單介紹一下在被keep-alive包含的組件/路由中,會多出兩個生命周期的鈎子:activated 與 deactivated。
文檔:在 2.2.0 及其更高版本中,activated 和 deactivated 將會在 樹內的所有嵌套組件中觸發。
activated在組件第一次渲染時會被調用,之后在每次緩存組件被激活時調用。
activated調用時機:
第一次進入緩存路由/組件,在mounted后面,beforeRouteEnter守衛傳給 next 的回調函數之前調用:
beforeMount=> 如果你是從別的路由/組件進來(組件銷毀destroyed/或離開緩存deactivated)=>mounted=> activated 進入緩存組件 => 執行 beforeRouteEnter回調
因為組件被緩存了,再次進入緩存路由/組件時,不會觸發這些鈎子:// beforeCreate created beforeMount mounted 都不會觸發。
deactivated:組件被停用(離開路由)時調用
使用了keep-alive就不會調用beforeDestroy(組件銷毀前鈎子)和destroyed(組件銷毀),因為組件沒被銷毀,被緩存起來了。
這個鈎子可以看作beforeDestroy的替代,如果你緩存了組件,要在組件銷毀的的時候做一些事情,你可以放在這個鈎子里。
如果你離開了路由,會依次觸發:
組件內的離開當前路由鈎子beforeRouteLeave => 路由前置守衛 beforeEach =>全局后置鈎子afterEach => deactivated 離開緩存組件 => activated 進入緩存組件(如果你進入的也是緩存路由)
<二> 在項目的具體應用
在開發中經常有從列表跳到詳情頁,然后返回詳情頁的時候需要緩存列表頁的狀態(比如滾動位置信息),這個時候就需要保存狀態,要緩存狀態;vue里提供了keep-alive組件用來緩存狀態。
可以用以下兩種方案解決問題;
一、利用meta標簽
直接上代碼
1、首先在路由中的meta標簽中記錄keepAlive的屬性為true
path: '/classify',
name: 'classify',
component: () => import('@/views/classify/classify.vue'),
meta: {
title: '雷石淘券券',
keepAlive: true
}
},
2、在創建router實例的時候加上scrollBehavior方法
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes,
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return {
x: 0,
y: 0
}
}
}
})
3、在需要緩存的router-view組件上包裹keep-alive組件
<keep-alive>
<router-view v-if='$route.meta.keepAlive'></router-view>
</keep-alive><router-view v-if='!$route.meta.keepAlive'></router-view>
4、由於有些情況下需要緩存有些情況下不需要緩存,可以在緩存的組件里的路由鈎子函數中做判斷
beforeRouteLeave (to, from, next) {
this.loading = true
if (to.path === '/goods_detail') {
from.meta.keepAlive = true
} else {
from.meta.keepAlive = false
// this.$destroy()
}
next()
},
支持可以支持組件的緩存,但是這種方法有bug,首先第一次打開頁面的時候並不緩存,即第一次從列表頁跳到詳情頁,再回來並沒有緩存,后面在進入詳情頁才會被緩存
並且只會緩存第一次進入的狀態,不會重新請求數據,如果當頁面A選中一個分類跳到B頁面,再從B列表頁面跳往詳情頁,此時會緩存這個狀態,並且以后再從A頁面的其他分類跳到B頁面都不會重新被緩存,以至於每次從詳情頁返回B頁面都會跳第一次緩存的狀態;當你的項目只有一種狀態需要緩存,可以考慮使用這種方法
二、使用include、exclude屬性和beforeRouteLeave鈎子函數和vuex (推薦使用)
這種方法將需要緩存的組件保存到全局變量 (在vuex中保存數據),可以在路由的鈎子函數里靈活的控制哪些組件需要緩存,那些不需要緩存
上代碼
1、在創建的router對象上加scrollBehavior方法,同上;
使用scrollBehavior , 主要針對移動端開發
2、將需要緩存的組件加在include屬性里
<keep-alive :include="catch_components">
<router-view></router-view>
</keep-alive>
3、在store里加入需要緩存的的組件的變量名,和相應的方法;
const state = {
catch_components: []
}
// 異步數據
const actions = {
// 增加路由
addRoute ({
commit,
rootState
}, data) {
const ArrList = rootState.keepAlive.catch_components
let common = false
ArrList.forEach(item => {
if (item === data) {
common = true
}
})
if (!common) {
ArrList.push(data)
}
commit('get_catch', ArrList)
},
// 移除指定路由
removeRoute ({
commit,
rootState
}, data) {
const ArrList = rootState.keepAlive.catch_components
let index = null
ArrList.forEach((item, num) => {
if (item === data) {
index = num
}
})
if (index) {
ArrList.splice(index, 1)
}
commit('get_catch', ArrList)
},
// 移除所有路由
removeAllRoute ({
commit,
rootState
}, data) {
commit('get_catch', [])
}
}
// 同步數據
const mutations = {
get_catch (state, data) {
state.catch_components = data
}
}
const getters = {
get_catch: state => state.catch_components
}
export default {
state,
mutations,
getters,
actions
}
4、在需要緩存的組件的beforeRouteLeave鈎子函數里控制需要緩存的組件
<1>采用vuex的輔助函數方式引入mapActions
import { mapActions } from 'vuex'
<2>給組件加name屬性
因為采用vue提供的include屬性,必須要求組件有name屬性,否則無法匹配到組件
export default {
name:'authDetail',
components: {
},
}
<3>在methods中把mapActions中的函數展開出來
methods: {
...mapActions(['addRoute', 'removeAllRoute', 'removeRoute']),
}
<4>配置beforeRouteLeave組件單獨的路由鈎子,傳入對應的組件name屬性為參數,以此使用include屬性動態控制組件添加keepAlive,以此達到控制緩存的目的
beforeRouteLeave (to, from, next) { // //要在離開該組件的時候控制需要緩存的組件,否則將出現第一次不緩存的情況
if (to.path) { // 去往詳情頁的時候需要緩存組件,其他情況下不需要緩存
this.addRoute('authDetail') // authDetail為需要緩存的組件的名字,include會動態匹配,添加keepAlive,這里相對於是給vuex的actions異步傳參數,類似this.$store.dispatch('xxx' , 傳參data數據)
}
next()
},
// 如果僅需要在此組件緩存,離開該組件后 , 需要清空緩存,讓頁面重新調接口重新加載數據 , 則需要在離開之前清空所有緩存,由於采用include數組的方式,所有只需要清空該組數里的name數據即可 , 直接調用方法即可,不傳參數
methods:{
// 返回
onClickLeft () {
this.removeAllRoute()
this.$router.go(-1)
},
}
<5>三個解釋
addRoute ({
commit,
rootState
}, data) {
const ArrList = rootState.keepAlive.catch_components
let common = false
ArrList.forEach(item => {
if (item === data) {
common = true
}
})
if (!common) {
ArrList.push(data)
}
commit('get_catch', ArrList)
},
思路是
遍歷vuex里面的state數據倉庫
如果通過this.addRoute('頁面name')傳參數進來
如果原來的state里面是存在這個數據的,說明有緩存,這個時候就不用加keepalive
如果沒有的話,即!common,即把他加入到數組中
// 注意 actions里面的寫法
actions:{
addRoute ({ commit,rootState}, data) {
const ArrList = rootState.keepAlive.catch_components
let common = false
ArrList.forEach(item => {
if (item === data) {
common = true
}
})
if (!common) {
ArrList.push(data)
}
commit('get_catch', ArrList)
},
}
// 解析
// 定義一個addRote方法 一般里面的參數是context,但這里細化了處理,直接使用commit作為參數,其實本質也是comtext,因為comit是可以通過context.commit點出來,context是個對象,所以這里面直接寫了{commit}
// 關於rootState,它是表示根store,意思是如果vuex里面的store使用modules來划分不同的子store,那這個rootState就相當於能拿到每一個子store里面的State的數據,他是一個根數據倉庫
在vuex的文檔中
actions對象分發分為以下兩種映射方式 一個為數組 一個為對象
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 將 `this.increment()` 映射為 `this.$store.dispatch('increment')`
// `mapActions` 也支持載荷:
'incrementBy' // 將 `this.incrementBy(amount)` 映射為 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 將 `this.add()` 映射為 `this.$store.dispatch('increment')`
})
}
}