在使用Vue開發管理系統項目的時候,為了保存頁面的瀏覽狀態,我們可以使用內置組件keep-alive
來緩存組件內部狀態,避免重新渲染。
<keep-alive>
<router-view></router-view>
</keep-alive>
被keep-alive包裹的動態組件或router-view會緩存不活動的實例,再次被調用這些被緩存的實例會被再次復用,而不需要再次發送HTTP請求。對於使用tabs標簽頁打開頁面時,這正是我們想要的效果。但是這樣做同時也存在一個問題,就是被keep-alive包裹的組件會保持最后一次請求數據的渲染結果,即使我們關閉了tabs頁,再次打開時依舊是上一次的狀態。這時,我們就得用上include、exclude屬性來對緩存的組件進行篩選。
Props:
include
- 字符串或正則表達式。只有名稱匹配的組件會被緩存。exclude
- 字符串或正則表達式。任何名稱匹配的組件都不會被緩存。
<!-- 逗號分隔字符串 --> <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>
有了include和exclude對組件進行篩選,然后動態的記錄tabs中打開的組件名,這就完美的解決了動態緩存的問題。所以在配置路由文件時,我們必須得先import組件,然后以組件名調用。
import HelloWorld from '@/components/HelloWorld'
routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld }
但是,在項目開發中,為了提升頁面加載的速度,一般會采用路由懶加載的方式加載路由組件,這時調用的組件不再是通過組件名,而是匿名組件。
{ path: '/home', name: 'home', component: resolve => require(['@/components/learn/home'], resolve) }
可是,官方文檔指出:
匹配首先檢查組件自身的 name 選項,如果 name 選項不可用,則匹配它的局部注冊名稱 (父組件 components 選項的鍵值)。匿名組件不能被匹配。
這樣一來include和exclude就無效了。。。
額(⊙o⊙)…目前我們項目就是這樣通過匿名組件調用的,所以就沒有使用keep-alive組件。這時領導試用系統,咦...我這打開的頁面怎么每次點進去都重置了頁面內容,怎么不能像打開網頁那樣,我看到哪就給保存那時的狀態呢,不過由於大家說弄不了,就把這個功能推掉了。
但是這段時間正好手中的事告一段落了,有空余時間,並本着有問題就得解決的原則,就自己琢磨起來。首先使用keep-alive進行全站緩存這個是肯定不行的,使用者肯定得說:怎么我把頁面關了打開后還是之前的呢。。。所以,感覺這是一個很復雜的問題,要不然早就加上了。
於是乎,打開度娘尋找心靈安慰。找吖找吖找,也不知在清一色的復制粘帖中摸索了多久,除了使用include來解決動態緩存問題,完全沒有對匿名組件的解決方案。就在快要放棄和度娘交流時,突然眼前閃過一絲希望的光芒!
終於發現了大神研究出的可行方案:Vue 全站緩存之 keep-alive : 動態移除緩存 https://juejin.im/post/5b610da4e51d45195c07720d。
在茫茫代碼中,他一眼就看到了隱藏在角落里的vue-component-12-keep-alive,然后才有了完美的解決方案。那就是使用Vue.mixin的方法攔截了路由離開事件,並在該攔截方法中實現了銷毀頁面緩存的功能。
// 全局混入,關閉tab時清除組件緩存 Vue.mixin({ beforeRouteLeave(to, from, next) { let flag = true this.$store.state.options.forEach(e => { // options存儲打開的tabs的組件路由 if(from.path == e.route) { flag = false } }) if(flag && this.$vnode.parent && this.$vnode.parent.componentInstance.cache) { let key = this.$vnode.key // 當前關閉的組件名 let cache = this.$vnode.parent.componentInstance.cache // 緩存的組件 let keys = this.$vnode.parent.componentInstance.keys // 緩存的組件名 if(cache[key] != null) { delete cache[key] let index = keys.indexOf(key) if(index > -1) { keys.splice(index, 1) } } } next() } })
不過在這之前我們還得解決一個問題,由於此方法是攔截了路由離開事件,而當我們關閉不是當前激活的標簽頁時是不會觸發路由離開事件的,這就會導致清除該緩存失效,所以我們得在關閉不激活的標簽頁時先模擬一次點擊事件才能達到預期的效果。
// 移除tab標簽 tabRemove (targetName) { let event = new Event('click') document.getElementById("tab-" + targetName).dispatchEvent(event) // 觸發點擊事件 this.$store.commit('delete_tabs', targetName); if (this.activeIndex === targetName) { // 設置當前激活的路由 if (this.openTab && this.openTab.length >= 1) { this.$store.commit('set_active_index', this.openTab[this.openTab.length - 1].route); this.$router.push({ path: this.activeIndex }); } else { this.$router.push({ path: '/' }); } } }
OK,這樣就完美了解決了匿名組件動態清除緩存的問題了。