keep-alive 元件級快取
前言
在Vue構建的單頁面應用(SPA)中,路由模組一般使用vue-router。vue-router不儲存被切換元件的狀態,
它進行push或者replace時,舊元件會被銷燬,而新元件會被新建,走一遍完整的生命週期。
但有時候,我們有一些需求,比如跳轉到詳情頁面時,需要保持列表頁的滾動條的深度,等返回的時候依然在這個位置,這樣可以提高使用者體驗。
在Vue中,對於這種“頁面快取”的需求,我們可以使用keep-alive元件來解決這個需求。
keep-alive
keep-alive是個抽象元件(或稱為功能型元件),實際上不會被渲染在DOM樹中。 它的作用是在記憶體中快取元件(不讓元件銷燬),等到下次再渲染的時候,還會保持其中的所有狀態,並且會觸發activated鉤子函式。 因為快取的需要通常出現在頁面切換時,所以常與router-view一起出現:<keep-alive> <router-view /> </keep-alive>
<keep-alive :include="['Home', 'User']"> <router-view /> </keep-alive>
vue實現前進重新整理,後退不重新整理
希望實現前進重新整理、後退不重新整理的效果。即載入過的介面能快取起來(返回不用重新載入),關閉的介面能被銷燬掉(再進入時重新載入)。
例如對a->b->c 前進(b,c)重新整理,c->b->a 後退(b,a)不重新整理
知道路由是前進還是後退就好了,
這樣的話我就能在後退的時候讓from
路由的keepAlive
置為false
,
to
路由的keepAlive
置為ture
,就能在再次前進時,重新載入之前這個keepAlive
被置為false
的路由了
但是這個需要集合鈎子函式來是實現
// App.vue <div class="app"> <keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view> </keep-alive> <router-view v-if="!$route.meta.keepAlive"></router-view> </div>
下面在router/index.js即我們的路由檔案中,定義meta資訊:
// list是我們的搜尋結果頁面 { path: '/list', name: 'List', component: resolve => require(['@/pages/list'], resolve), meta: { isUseCache: false, // 這個欄位的意思稍後再說 keepAlive: true // 通過此欄位判斷是否需要快取當前元件 } }
說這之前,先簡單說一下和快取相關的vue鉤子函式。
設定了keepAlive快取的元件:
第一次進入:beforeRouterEnter ->created->…->activated->…->deactivated
後續進入時:beforeRouterEnter ->activated->deactivated
可以看出,只有第一次進入該元件時,才會走created鉤子,而需要快取的元件中activated是每次都會走的鉤子函式。
所以,我們要在這個鉤子裡面去判斷,當前元件是需要使用快取的資料還是重新重新整理獲取資料。思路有了,下面我們來實現
// list組價的activated鉤子 activated() { // isUseCache為false時才重新重新整理獲取資料 // 因為對list使用keep-alive來快取元件,所以預設是會使用快取資料的 if(!this.$route.meta.isUseCache){ this.list = []; // 清空原有資料 this.onLoad(); // 這是我們獲取資料的函式 } }
這裡的isUseCache其實就是我們用來判斷是否需要使用快取資料的欄位,我們在list的路由的meta中已經預設設定為false,所以第一次進入list時是獲取資料的。
當我們從詳情頁返回時,我們把list頁面路由的isUseCache設定成true,這樣我們在返回list頁面時會使用快取資料
// 詳情頁面的beforeRouteLeave鉤子函式 beforeRouteLeave (to, from, next) { if (to.name == 'List') { to.meta.isUseCache = true; } next(); }
我們這裡是在即將離開detail頁面前判斷是否返回的列表頁。
如果是返回list頁面,則把list頁面路由的isUseCache欄位設定成true。為什麼這樣設定呢?
因為我們對list元件使用的keep-alive進行快取元件,其預設就是使用快取的。
而我們又在list元件的actived鉤子函式中進行了判斷:
只有在list頁面的isUseCache==false時才會清空原有資料並重新獲取資料。
所以此處設定isUseCache為true,此時返會list頁面是不會重新獲取資料的,而是使用的快取資料。
detail返回list可以快取資料了,那麼search前往list頁面時怎麼讓list頁面不使用快取資料而是獲取新資料呢?我們重新回到list的activated鉤子中:
// list組價的activated鉤子 activated() { // isUseCache為false時才重新重新整理獲取資料 // 因為對list使用keep-alive來快取元件,所以預設是會使用快取資料的 if(!this.$route.meta.isUseCache){ this.list = []; // 清空原有資料 this.onLoad(); // 這是我們獲取資料的函式 this.$route.meta.isUseCache = false; } }
我們加了一行this.$route.meta.isUseCache=false;也就是從detail返回list後,將list的isUseCache欄位為false,
而從detail返回list前,我們設定了list的isUseCache為true。
所以,只有從detail返回list才使用快取資料,而其他頁面進入list是重新重新整理資料的。
至此,一個前進重新整理、後退返回的功能基本完成了
場景還原實際
比如,如果這個詳情頁是個訂單詳情,那麼在訂單詳情頁可能會有刪除訂單的操作。 那麼刪除訂單操作後會返回訂單列表頁,是需要列表頁重新重新整理的。 那麼我們需要此時在訂單詳情頁進行是否要重新整理的判斷。簡單改造一下詳情頁:data () { return { isDel: false // 是否進行了刪除訂單的操作 } }, beforeRouteLeave (to, from, next) { if (to.name == 'List') { // 根據是否刪除了訂單的狀態,進行判斷list是否需要使用快取資料 to.meta.isUseCache = !this.isDel; } next(); }, methods: { deleteOrder () { // 這裡是一些刪除訂單的操作 // 將狀態變為已刪除訂單 // 所以beforeRouteLeave鉤子中就會將list元件路由的isUseCache設定為false // 所以此時再返回list時,list是會重新重新整理資料的 this.isDel = true; this.$router.go(-1) } }
另外用Vuex來實現後退功能
然後在一篇部落格中看到是用Vuex來寫的,所以這邊也自己demo了下:
就是下面的程式碼了:
實現條件快取:全域性的include陣列
只需要將B動態地從include陣列中增加/刪除就行了
在Vuex中定義一個全域性的快取陣列,待傳給include:
export default { namespaced: true, state: { keepAliveComponents: [] // 快取陣列 }, mutations: { keepAlive (state, component) { // 注:防止重複新增(當然也可以使用Set) !state.keepAliveComponents.includes(component) && state.keepAliveComponents.push(component) }, noKeepAlive (state, component) { const index = state.keepAliveComponents.indexOf(component) index !== -1 && state.keepAliveComponents.splice(index, 1) } } }
在父頁面中定義keep-alive,並傳入全域性的快取陣列:
// App.vue <div class="app"> <!--傳入include陣列--> <keep-alive :include="keepAliveComponents"> <router-view></router-view> </keep-alive> </div> export default { computed: { ...mapState({ keepAliveComponents: state => state.global.keepAliveComponents }) } }
快取:在路由配置頁中,約定使用meta屬性keepAlive,值為true表示元件需要快取。
在全域性路由鉤子beforeEach中對該屬性進行處理,這樣一來,每次進入該元件,都進行快取:
const router = new Router({ routes: [ { path: '/A/B', name: 'B', component: B, meta: { title: 'B頁面', keepAlive: true // 這裡指定B元件的快取性 } } ] }) router.beforeEach((to, from, next) => { // 在路由全域性鉤子beforeEach中,根據keepAlive屬性,統一設定頁面的快取性 // 作用是每次進入該元件,就將它快取 if (to.meta.keepAlive) { store.commit('global/keepAlive', to.name) } })
取消快取的時機:對快取元件使用路由的元件層鉤子beforeRouteLeave。
因為B->A->B時不需要快取B,所以可以認為:當B的下一個頁面不是C時取消B的快取,那麼下次進入B元件時B就是全新的:
export default { name: 'B', created () { // ...設定滾動條在最頂部 }, beforeRouteLeave (to, from, next) { // 如果下一個頁面不是詳情頁(C),則取消列表頁(B)的快取 if (to.name !== 'C') { this.$store.commit('global/noKeepAlive', from.name) } next() } }
因為B的條件快取,是B自己的職責,所以最好把該業務邏輯寫在B的內部,而不是A中,這樣不至於讓元件之間的跳轉關係變得混亂。
一個需要注意的細節:因為keep-alive元件的include陣列操作的物件是元件名、而不是路由名,
因此我們定義每一個元件時,都要顯式宣告name屬性,否則快取不起作用。而且,一個顯式的name對Vue devtools有提示作用。