feb-alive
github地址
體驗鏈接
Vue頁面級緩存解決方案feb-alive (上)
在剖析feb-alive實現之前,希望大家對以下基本知識有一定的了解。
keep-alive實現原理 history api vue渲染原理 vue虛擬dom原理feb-alive與keep-alive差異性
1. 針對activated鉤子差異性
keep-alive配合vue-router在動態路由切換的情況下不會觸發activated鉤子,因為切換的時候組件沒有變化,所以只能通過beforeRouteUpdate鉤子或者監聽$route來實現數據更新,而feb-alive在動態路由切換時,依然會觸發activated鉤子,所以用戶可以放心的將業務更新邏輯寫在activated鉤子,不必關心動態路由還是非動態路由的情況。
2. feb-alive是頁面級緩存,而keep-alive是組件級別緩存
所以在上文中講到的使用keep-alive存在的一些限制問題都能夠得到有效的解決
實現原理
首先我們的目標很明確,需要開發的是一個頁面級別的緩存插件,之前使用keep-alive遇到的諸多問題,歸根結底是因為它是一個組件級別的緩存。那么我們就需要尋找每個頁面的特征,用來存儲我們需要存儲的路由組件vnode,這里我們就需要思考什么可以作為每個頁面的標記
兩種方式:
通過每個url的查詢參數來存儲key 通過history.state來存儲key方案一:使用查詢參數
優點:
可以兼容vue-router的hash模式
缺點:
每個頁面的url后面都會帶一個查詢參數
每次頁面跳轉都需要重寫url
方案二:使用history.state
優點:
無需附帶額外的查詢參數
缺點:
不支持hash模式
相比方案一明顯的缺點,我更較傾向于方案二,舍棄hash模式的兼容性,換來整個插件更加好的用戶體驗效果。
接下來看下feb-alive的實現,feb-alive組件與上文的keep-alive一樣都是抽象組件,結構基本一致,主要區別在于render函數的
實現
// feb-alive/src/components/feb-alive.jsrender () { // 取到router-view的vnode const vnode = this.$slots.default ? this.$slots.default[0] : null const disableCache = this.$route.meta.disableCache // 如果不支持html5 history則不做緩存處理 if (!supportHistoryState) { return vnode } // 嘗試寫入key if (!history.state || !history.state[keyName]) { const state = { [keyName]: genKey() } const path = getLocation() history.replaceState(state, null, path) } // 有些瀏覽器不支持往state中寫入數據 if (!history.state) { return vnode } // 指定不使用緩存 if (disableCache) { return vnode } // 核心邏輯 if (vnode) { const { cache, keys } = this const key = history.state[keyName] const { from, to } = this.$router.febRecord let parent = this.$parent let depth = 0 let cacheVnode = Object.create(null) vnode && (vnode.data.febAlive = true) while (parent && parent._routerRoot !== parent) { if (parent.$vnode && parent.$vnode.data.febAlive) { depth++ } parent = parent.$parent } // 記錄緩存及其所在層級 febCache[depth] = cache // /home/a backTo /other // 內層feb-alive實例會被保存,防止從/home/a 跳轉到 /other的時候內層feb-alive執行render時候,多生成一個實例 if (to.matched.length < depth + 1) { return null } if (from.matched[depth] === to.matched[depth] && (from.matched.slice(-1)[0] !== to.matched.slice(-1)[0])) { // 嵌套路由跳轉 && 父級路由 // /home/a --> /home/b // 父路由通過key進行復用 cache[key] = cache[key] || this.keys[this.keys.length - 1] cacheVnode = getCacheVnode(cache, cache[key]) if (cacheVnode) { vnode.key = cacheVnode.key remove(keys, key) keys.push(key) } else { this.cacheClear() cache[key] = vnode keys.push(key) } } else { // 嵌套路由跳轉 && 子路由 // 正常跳轉 && 動態路由跳轉 // /a --> /b // /page/1 --> /page/2 vnode.key = `__febAlive-${key}-${vnode.tag}` cacheVnode = getCacheVnode(cache, key) // 只有相同的vnode才允許復用組件實例,否則雖然實例復用了,但是在patch的最后階段,會將復用的dom刪除 if (cacheVnode && vnode.tag === cacheVnode.tag) { // 從普通路由后退到嵌套路由時,才需要復原key vnode.key = cacheVnode.key vnode.componentInstance = cacheVnode.componentInstance remove(keys, key) keys.push(key) } else { this.cacheClear() cache[key] = vnode keys.push(key) } } vnode.data.keepAlive = true } return vnode}
新聞熱點
疑難解答
圖片精選