路總歸是有的,就看願不願意劍走偏鋒了。
場景
在最近的開發中,設計有A、B、C三個頁面,試想這樣一個場景需求:
- 離開B頁面進入C頁面,快取B頁面資料(keepAlive: true)
- 離開B頁面進入A頁面,不快取B頁面資料(keepAlive: false)
概念
-
keep-alive
- keep-alive 包裹動態元件時,會快取不活動的元件例項,而不是銷燬它們。和 transition 相似,keep-alive 是一個抽象元件:它自身不會渲染一個 DOM 元素,也不會出現在父元件鏈中。
- keep-alive: vue文件
-
元件內的守衛 - beforeRouteLeave
- 導航離開該元件的對應路由時呼叫
- 可以訪問元件例項
this
- 元件內的守衛: vue-router 文件
前置背景:keep-alive 元件實現
- 路由元資訊內新增特定欄位如:keepAlive
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: {
keepAlive: true
}
}
]
}
]
})
複製程式碼
- 父元件內根據路由中的keepAlive欄位動態使用keep-alive標籤
class Home extends Vue {
get keepAlive () {
// 獲取當前路由的元資訊中的keepAlive欄位
return this.$route.meta.keepAlive
}
private render () {
return (
<div>
{
!this.keepAlive && <router-view />
}
<keep-alive>
{
this.keepAlive && <router-view/>
}
</keep-alive>
</div>
)
}
}
export default Home
複製程式碼
思路
由於現在元件的keep-alive是動態根據路由元資訊中的keepAlive欄位進行動態使用的,所以只要動態改變對應路由元資訊的keepAlive欄位就可以實現動態快取。
實現方案
方案一
- 利用beforeRouteLeave改變from的keepAlive實現(原思路,網路解決方案之一,有bug)
beforeRouteLeave (to: any, from: any, next: any) {
// 導航離開該元件的對應路由時呼叫
// 判斷是否是去往頁面 C
if (to.name !== 'C') {
// 不是去 C 頁面,不快取
from.meta.keepAlive = false
} else {
// 是去 C 頁面,快取
from.meta.keepAlive = true
}
next()
}
複製程式碼
bug:首次去C頁面,再返回B頁面,B並沒有快取,第二次再進入C頁面,B頁面快取,且進A頁面並不能清除B頁面的快取
方案二(網路方案)
- $destroy()銷燬
beforeRouteLeave (to: any, from: any, next: any) {
// 導航離開該元件的對應路由時呼叫
// 判斷是否是去往頁面 C
if (to.name !== 'C') {
// 不是去 C 頁面,不快取
this.$destroy()
}
next()
}
複製程式碼
bug:銷燬之後永遠不會被快取
方案三(網路方案)
- 根據原始碼看來快取的元件都會設定一個cache屬性,可以通過程式碼強行移除掉。缺點就是沒有徹底銷燬依舊佔記憶體
- 具體實現參考
方案四(最優解)
- 利用keep-alive的include屬性,利用vuex動態控制include達到動態管理快取
- 這邊不做贅述,網上有很多相關示例程式碼,且原理很簡單
特殊場景以上方案均不可實現下的解決方案
特殊場景
- 此次專案為移動端專案,並未用到keep-alive的include屬性,且如果要加入,則專案很多配置需要修改,比較麻煩
解決方案
- 解決方案與上面的方案一類似
- 區別:
- 不操作beforeRouteLeave中的from物件改變keepAlive
- 直接操作this.$router中對應路由元資訊的keepAlive
// 操作指定name的路由的元資訊
private changeKeepAlive (parentName: string, name: string, keepAlive: boolean) {
// @ts-ignore
this.$router.options.routes.map((item: any) => {
if (item.name === parentName) {
item.children.map((a: any) => {
if (a.name === name) {
a.meta.keepAlive = keepAlive
}
})
}
})
}
beforeRouteLeave (to: any, from: any, next: any) {
// 導航離開該元件的對應路由時呼叫
// 可以訪問元件例項 `this`
if (to.name === 'C') {
this.changeKeepAlive('Home', 'B', true)
} else {
this.changeKeepAlive('Home', 'B', false)
}
next()
}
複製程式碼
經測試,這種解決方案就不會出現方案一的bug
BY--LucaLJX (LucaLJX的github)