首先這個交互我也叫不出專業的名字,大體場景是這樣的:vue
SomePage -> List: List從新加載
List -> Info -> List: List使用緩存
複製代碼
SomePage -> Form: Form從新加載
Form -> Address -> Form: Form使用緩存
複製代碼
以上是簡單舉個例子,實際上這相似我在上家公司作移動端(SPA)時碰到的問題,只不過咱們是商家列表,但交互相似,剛開始我是直接用路由方式解決的,相似將Info做爲List的子頁面,將Address做爲Form的子頁面。實現是實現了,但太麻煩了,維護麻煩啊😂,而且對組內其它夥伴不太友好。直到某一次我決定對其進行重構,重構前也沒有什麼好思路,而後Google了一番茅塞頓開:利用keep-alive的include實現,當時知道了思路,我就沒往下看了,本身動手豐衣足食😁。node
統一一下詞彙,下文中說的類列表頁
就是List,類詳情頁
就是Infowebpack
剛開始我還踩了一個坑,主要是Vue的keep-alive當時不多接觸,都是Copy......git
首先按照以前使用(copy)keep-alive
的套路是像這樣的:github
<keep-alive>
<router-view v-if="$route.meta.keepAlive"/>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"/>
複製代碼
而後我將它改形成這樣web
<keep-alive>
<router-view v-if="$route.meta.keepAlive"/>
</keep-alive>
<keep-alive :include="include">
<router-view v-if="!$route.meta.keepAlive"/>
</keep-alive>
複製代碼
就是控制這個include
,具體的作法是,先在類列表頁的路由選項meta
中增長一個cacheTo
字段,表示對指定頁面緩存,值爲一個數組,而後:vue-cli
router.beforeEach((to, from, next) => {
if (isPageLikeList(from)) {
// 若是from是類列表頁面
const fromCacheTo = from.meta.cacheTo
if (fromCacheTo.indexOf(to.name) > -1) {
// 若是to是類詳情頁面
// 將from對應的組件設置緩存
} else {
// 移除from緩存
}
}
// ...
})
複製代碼
但想的很美,寫完發現Bug比較嚴重,類列表頁緩存老失效,爲何呢?這就要從keep-alive的include機制提及了,因而看了一下keep-alive的源碼,源碼中有這麼一段npm
// ...
mounted () {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
// ...
複製代碼
而後繼續往下找偵聽include
的回調邏輯數組
function pruneCache (keepAliveInstance: any, filter: Function) {
// 對於include,filter邏輯爲:include包含組件名時返回true,不然返回false
const { cache, keys, _vnode } = keepAliveInstance
for (const key in cache) {
const cachedNode: ?VNode = cache[key]
if (cachedNode) {
const name: ?string = getComponentName(cachedNode.componentOptions)
// 若組件不在include範圍中
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
}
}
}
function pruneCacheEntry ( cache: VNodeCache, key: string, keys: Array<string>, current?: VNode ) {
const cached = cache[key]
// 銷燬組件
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy()
}
// 清除組件的緩存
cache[key] = null
remove(keys, key)
}
複製代碼
大體流程是,include
變化後(不論是新增了仍是減小了),若是組件不在include
中而且當前的cache[組件name]
是有緩存的,就執行pruneCacheEntry,銷燬組件並清除組件的緩存。
那麼這對我上述的邏輯有什麼影響呢?按照我以前的流程,動態控制include
,這樣作刪除組件緩存是沒有問題,可是新增就會有問題了,由於新增操做發生在從類列表頁離開進入到類詳情頁以前,此時類列表頁已經存在了,而後源碼中偵聽include
的流程中也只有清除緩存的操做。
知道了問題,如何解決就很清晰了
咱們能夠將對include
的操做放到類列表頁進入前(router.beforeEach),這樣就能有效緩存類列表頁了,其實這東西就是這麼簡單,先無論三七二十一,只要是類列表頁,進入以前都將其緩存,離開時判斷新路由是不是該類列表頁指定的類詳情頁,若是不是就清除緩存。那麼在實現以前,我們先將場景再細化一下,以前簡直比玩具還玩具......
SomePage -> List: List從新加載
// 第一種
List -> Info -> List: List使用緩存
// 第二種
List -> Info -> SomePage -> List: List從新加載
// 第三種,不限層級
List -> Info -> OtherList -> OtherInfo -> ... -> List: List、Info、OtherList...使用緩存
其中第三種,從OtherInfo返回OtherList時,OtherInfo應該是要被清除緩存的,依次類推。也就是說,返回時不保留以前的緩存
複製代碼
如今,咱們根據例舉的場景將大體邏輯肯定一下。這裏有一個點是很明確的,那就是: 咱們只須要關心路由切換時的to
和from
,從這2個路由對象着手去解決。那麼這裏能夠例舉出4中狀況:
to
和from
都不是類列表頁to
和from
都是類列表頁to
是類列表頁from
是類列表頁如今根據這4種狀況細化一下
to
和from
都不是類列表頁
to
和from
都是類列表頁
to
不在from
的配置中,清空緩存,同時要新增to
緩存;from
的緩存,新增to
緩存to
是類列表頁
from
不在to
的配置中,清空緩存,新增to
緩存to
不在from
的配置中,清空緩存如何判斷是不是類列表頁?
[
{
path: '/list',
name: 'list',
meta: {
cacheTo: ['info']
}
// ...
},
{
path: '/info',
name: 'info',
// ...
}
]
複製代碼
如上,在路由中維護一個字段如cacheTo
,若是配置了組件名,就認爲是類列表頁面
邏輯理清楚咯,接下來具體實現,咱們將它作得通用一點(可拔插),而且儘可能保證對原項目有較小的侵入性,我將它命名爲VKeepAliveChain
(緩存鏈)
首先,若是像踩坑記那樣維護include
,不太具有可拔插的特性,我還得去搞一個store
,那麼這個include
可使用Vue.observable
處理
// VKeepAliveChain.js
const state = Vue.observable({
caches: []
})
const clearCache = () => {
if (state.caches.length > 0) {
state.caches = []
}
}
const addCache = name => state.caches.push(name)
複製代碼
爲了不像踩坑記那樣直接使用<keep-alive :include="include">
,咱們實現一個函數式組件來解決
// VKeepAliveChain.js
export const VKeepAliveChain = {
install (Vue, options = { key: 'cacheTo' }) {
const { key } = options
// 支持一下自定義key
if (key) {
cacheKey = key
}
// 直接透傳children,因此會像keep-alive同樣,只拿第一個組件節點
const component = {
name: 'VKeepAliveChain',
functional: true,
render (h, { children }) {
return h(
'keep-alive',
{ props: { include: state.caches } },
children
)
}
}
Vue.component('VKeepAliveChain', component)
}
}
複製代碼
如今咱們來實現緩存控制的主要邏輯,因爲要利用router.beforeEach
,約定了儘可能小的侵入性,這裏能夠merge一下
// VKeepAliveChain.js
const defaultHook = (to, from, next) => next()
export const mergeBeforeEachHook = (hook = defaultHook) => {
return (to, from, next) => {
// 緩存控制邏輯
// 1. 都不是類列表頁
// 清空緩存
// 2. 都是類列表頁
// 若`to`不在`from`的配置中,清空緩存,同時要新增`to`緩存
// 保留`from`的緩存,新增`to`緩存
// 3. 新路由是類列表頁
// 若`from`不在`to`的配置中,清空緩存,新增`to`緩存
// 不然,無需處理
// 4. 舊路由是類列表頁
// 若`to`不在`from`的配置中,清空緩存
const toName = to.name
const toCacheTo = (to.meta || {})[cacheKey]
const isToPageLikeList = toCacheTo && toCacheTo.length > 0
const fromName = from.name
const fromCacheTo = (from.meta || {})[cacheKey]
const isFromPageLikeList = fromCacheTo && fromCacheTo.length > 0
if (!isToPageLikeList && !isFromPageLikeList) {
clearCache()
} else if (isToPageLikeList && isFromPageLikeList) {
if (fromCacheTo.indexOf(toName) === -1) {
clearCache()
}
addCache(toName)
} else if (isToPageLikeList) {
if (toCacheTo.indexOf(fromName) === -1) {
clearCache()
addCache(toName)
}
} else if (isFromPageLikeList) {
if (fromCacheTo.indexOf(toName) === -1) {
clearCache()
}
}
return hook(to, from, next)
}
}
複製代碼
那麼整個緩存鏈的功能就實現了,同時我將它發不到了npm v-keep-alive-chain上。
首先引入並註冊它
// main.js
import { mergeBeforeEachHook, VKeepAliveChain } from 'v-keep-alive-chain'
Vue.use(VKeepAliveChain, {
key: 'cacheTo' // 可選的 默認爲cacheTo
})
// 若是你沒有註冊過beforeEach
router.beforeEach(mergeBeforeEachHook())
// 若是有註冊beforeEach
router.beforeEach(mergeBeforeEachHook((to, from, next) => {
next()
}))
複製代碼
而後在App.vue(視你的狀況而定)中
<keep-alive>
<router-view v-if="$route.meta.keepAlive"/>
</keep-alive>
<VKeepAliveChain>
<router-view v-if="!$route.meta.keepAlive"/>
</VKeepAliveChain>
複製代碼
接着在router中配置你的需求
[
{
path: '/list',
name: 'list',
meta: {
cacheTo: ['info']
}
// ...
},
{
path: '/info',
name: 'info',
// ...
}
]
複製代碼
而後就能愉快的玩耍了
name
屬性,而且這個name
須要和組件name
同樣cacheTo
優先級小於keepAlive
,因此,處理這種需求的頁面不要設置keepAlive
文章寫的比較快,若有什麼錯誤,能夠下方留言咯
朋友,看到這裏,但願文章對你有啓發,本人很是歡迎技術交流,若是你以爲文章對你有用,還請給老弟一個👍,平時我是不在意這些個的,但因爲我立刻要投遞簡歷了,須要點東西撐門面,沒得辦法,履歷太差了,謝謝你,願生活帶給你美好!!!