vue單頁緩存方案分析及實(shí)現(xiàn)
實(shí)現(xiàn)全站的頁面緩存,前進(jìn)刷新,返回走緩存,并且能記住上一頁的滾動(dòng)位置,參考了很多技術(shù)實(shí)現(xiàn),github上的導(dǎo)航組件實(shí)現(xiàn)的原理要么使用的keep-alive,要么參考了keep-alive的源碼,但是只用keep-alive沒法實(shí)現(xiàn)相同path,不同參數(shù)展示不同view,這就有點(diǎn)坑了,所以需要結(jié)合自己要實(shí)現(xiàn)的功能,適當(dāng)改造keep-alive,為了實(shí)現(xiàn)每次前進(jìn)都能刷新,返回走緩存還能自動(dòng)定位的功能,文章陸續(xù)從以下幾個(gè)方面展開講:兩套技術(shù)方案可選,最后定的技術(shù)方案的原因,實(shí)現(xiàn)的功能和原理,踩過的坑
方案一:vue的keep-alive組件
具體使用如下:
<keep-alive max="10"> <router-view/> </keep-alive>
為什么這么使用?
如vue官網(wǎng)(https://cn.vuejs.org/v2/api/#keep-alive)介紹:
<keep-alive> 包裹動(dòng)態(tài)組件時(shí),會(huì)緩存不活動(dòng)的組件實(shí)例,而不是銷毀它們。和 <transition> 相似,<keep-alive> 是一個(gè)抽象組件:它自身不會(huì)渲染一個(gè) DOM 元素,也不會(huì)出現(xiàn)在父組件鏈中。
當(dāng)組件在 <keep-alive> 內(nèi)被切換,它的 activated 和 deactivated 這兩個(gè)生命周期鉤子函數(shù)將會(huì)被對(duì)應(yīng)執(zhí)行。主要用于保留組件狀態(tài)或避免重新渲染。
因?yàn)榫彺娴男枰ǔ3霈F(xiàn)在切換頁面時(shí),所以就需要結(jié)合vue-router的router-view來實(shí)現(xiàn)
為什么keep-alive能實(shí)現(xiàn)緩存?
render () { const slot = this.$slots.default const vnode: VNode = getFirstComponentChild(slot) const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions if (componentOptions) { // check pattern const name: ?string = getComponentName(componentOptions) const { include, exclude } = this if ( // not included (include && (!name || !matches(include, name))) || // excluded (exclude && name && matches(exclude, name)) ) { return vnode } const { cache, keys } = this const key: ?string = vnode.key == null // same constructor may get registered as different local components // so cid alone is not enough (#3269) ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') : vnode.key if (cache[key]) { vnode.componentInstance = cache[key].componentInstance // make current key freshest remove(keys, key) keys.push(key) } else { cache[key] = vnode keys.push(key) // prune oldest entry if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode) } } vnode.data.keepAlive = true } return vnode || (slot && slot[0]) }
如上keep-alive源碼,其中render函數(shù)是這樣實(shí)現(xiàn)的,要渲染的試圖組件作為插槽內(nèi)容被獲取到,當(dāng)渲染到路徑匹配到的視圖組件時(shí)會(huì)根據(jù)vnode存儲(chǔ)的內(nèi)容拿到對(duì)應(yīng)的name,一次將這些組件實(shí)例放到變量cache中,這樣根據(jù)路由就可以找到緩存的vnode,返回給createComponent方法去執(zhí)行initComponent,vue組件渲染這塊的代碼如下
function initComponent (vnode, insertedVnodeQueue) { if (isDef(vnode.data.pendingInsert)) { insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert) vnode.data.pendingInsert = null } vnode.elm = vnode.componentInstance.$el if (isPatchable(vnode)) { invokeCreateHooks(vnode, insertedVnodeQueue) setScope(vnode) } else { // empty component root. // skip all element-related modules except for ref (#3455) registerRef(vnode) // make sure to invoke the insert hook insertedVnodeQueue.push(vnode) } }
這里會(huì)有 vnode.elm 緩存了 vnode 創(chuàng)建生成的 DOM 節(jié)點(diǎn)。所以對(duì)于首次渲染而言,除了在 <keep-alive> 中建立緩存,和普通組件渲染沒什么區(qū)別。從進(jìn)入到返回的大致執(zhí)行流程如下
能實(shí)現(xiàn)的功能
能夠把要緩存的組件渲染的vnode記到cache里邊,當(dāng)返回的時(shí)候用緩存里邊的dom直接渲染,還有keep-alive組件提供的include 和 exclude屬性,可以有條件的緩存想緩存的組件,如果配置了 max 并且緩存的長(zhǎng)度超過了這個(gè)max的值,還要從緩存中刪除第一個(gè)
存在的問題
存在的問題是存儲(chǔ)vnode節(jié)點(diǎn)的key是name,也就是定義路由時(shí)組件對(duì)應(yīng)的name,這就會(huì)導(dǎo)致同樣的path,不同參數(shù)的時(shí)候打開的是從cache里邊拿到的vnode,會(huì)渲染出同樣的視圖出來,但是很多業(yè)務(wù)場(chǎng)景都是根據(jù)參數(shù)來顯示不同內(nèi)容,而keep-alive底層并沒有對(duì)此做擴(kuò)展,可以看下keep-alive源碼
const key: ?string = vnode.key == null ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') : vnode.key if (cache[key]) { vnode.componentInstance = cache[key].componentInstance // make current key freshest remove(keys, key) keys.push(key) } else { cache[key] = vnode keys.push(key) // prune oldest entry if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode) } }
vnode.key就是路由里邊定義的name,所以要用這套方案來實(shí)現(xiàn)的根據(jù)不同參數(shù)展示不同視圖的功能就要對(duì)這里的key做改造,但是keep-alive是vue自帶的,沒法改底層,然后就誕生了我的第二套方案
方案二:navigation組件,scrollbehavior
github上找到類似功能的組件vue-navigation,這個(gè)vue組件可以實(shí)現(xiàn)返回走緩存,底層原理跟keep-alive一樣,實(shí)際上是改寫了keep-alive組件,前進(jìn)刷新時(shí)新增了一個(gè)參數(shù)VNK,這樣在路由發(fā)生變化的時(shí)候都會(huì)用給url帶一個(gè)參數(shù),并且cache的key取值依賴這個(gè)參數(shù),借鑒這個(gè)組件的思路,做了一個(gè)類似keep-alive的組件,其中key的值是getKey方法獲取的,改寫以后的render方法如下
render () { var vnode = this.$slots.default ? this.$slots.default[0] : null if (vnode) { vnode.key = vnode.key || (vnode.isComment ? 'comment' : vnode.tag) const { cache, keys } = this var key = getKey(this.$route, keyName) if (vnode.key.indexOf(key) === -1) { vnode.key = '__navigation-' + key + '-' + vnode.key } if (cache[key]) { if (vnode.key === cache[key].key) { vnode.componentInstance = cache[key].componentInstance } else { cache[key].componentInstance.$destroy() cache[key] = vnode } remove(keys, key) keys.push(key) } else { cache[key] = vnode keys.push(key) // prune oldest entry if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode) } } vnode.data.keepAlive = true } return vnode }
getKey方法實(shí)現(xiàn)
//url上新增參數(shù)vnk的值 export function genKey() { // const t = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' const t = 'xxxxxxxx' return t.replace(/[xy]/g, function (c) { const r = Math.random() * 16 | 0 const v = c === 'x' ? r : (r & 0x3 | 0x8) return v.toString(16) }) } // export function getKey(route, keyName) { return `${route.name || route.path}?${route.query[keyName]}` }
通過新寫一個(gè)install方法掛載這個(gè)導(dǎo)航組件到vue上就可以實(shí)現(xiàn)前進(jìn)刷新,返回走緩存,并且可以配置最大緩存數(shù),后續(xù)開源到github
最后剩下返回上一頁記住上一頁的位置,之所以沒有用開源的這個(gè)組件的記位置,是因?yàn)橹苯犹子眯枰恼w布局,height:100%;樣式造成$(windows).scrollTop失效,整體考慮改造成本較大,還是使用了vue-router提供的scrollBehavior,在路由配置里引入
實(shí)現(xiàn)如下:
var scrollBehavior = async (to, from, savedPosition) => { if (savedPosition) { return savedPosition } else { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ x: 0, y: to.meta.savedPosition || 0 }) }, 300) }) } } const router = new VueRouter({ mode: 'history', scrollBehavior, routes: [{ path: '', redirect: '/mobile/home.html', meta: { needMtaReport: true, parentsStyle: { height: '100%', minHeight: '100%' } } }, { name: 'scienceCompetition', path: '/mobile/scienceCompetition.html', component: scienceCompetition }] }
總結(jié):
1.單頁緩存下js加載解析編譯執(zhí)行的時(shí)間縮短了,返回的時(shí)候由于走緩存js腳本的占用時(shí)間完全可以忽略,從而整體上縮減了頁面的加載渲染時(shí)間
2. 因?yàn)轫?xiàng)目以前不是單頁,代碼里邊定義了很多全局變量或者全局事件綁定,改成單頁后全局變量的值依然存在,就會(huì)導(dǎo)致業(yè)務(wù)邏輯出現(xiàn)bug,所以使用單頁需要注意全局變量或是事件的謹(jǐn)慎使用,具體的踩坑記錄在http://www.dbjr.com.cn/article/147957.htm
3.通過push進(jìn)入下一頁時(shí),head里邊會(huì)累加前面頁面的靜態(tài)資源,訪問的頁面越多,最后的頁面掛的靜態(tài)的資源越多,返回的時(shí)候并不會(huì)減少已經(jīng)加載的靜態(tài)資源,單頁緩存是典型的空間換時(shí)間的方案,內(nèi)存的開銷比較大,能否對(duì)資源動(dòng)態(tài)增減以及內(nèi)存占用的優(yōu)化一直在探索中,暫時(shí)沒有找到很好的解決方法。。。。。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
解決vue-cli單頁面手機(jī)應(yīng)用input點(diǎn)擊手機(jī)端虛擬鍵盤彈出蓋住input問題
今天小編就為大家分享一篇解決vue-cli單頁面手機(jī)應(yīng)用input點(diǎn)擊手機(jī)端虛擬鍵盤彈出蓋住input問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-08-08vue-element的select下拉框賦值實(shí)例
這篇文章主要介紹了vue-element的select下拉框賦值實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03vue2.0構(gòu)建單頁應(yīng)用最佳實(shí)戰(zhàn)
這篇文章主要為大家分享了vue2.0構(gòu)建單頁應(yīng)用最佳實(shí)戰(zhàn)案例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04vscode中使用vue的一些插件總結(jié)(方便開發(fā))
對(duì)于很多使用vscode編寫vue項(xiàng)目的新手同學(xué)來說,可能不知道使用什么插件,下面這篇文章主要給大家介紹了關(guān)于vscode中使用vue的一些插件,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11在vue中axios設(shè)置timeout超時(shí)的操作
這篇文章主要介紹了在vue中axios設(shè)置timeout超時(shí)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09vue常用指令實(shí)現(xiàn)學(xué)生錄入系統(tǒng)的實(shí)戰(zhàn)
本文主要介紹了vue常用指令實(shí)現(xiàn)學(xué)生錄入系統(tǒng)的實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02Electron-vue腳手架改造vue項(xiàng)目的方法
這篇文章主要介紹了Electron-vue腳手架改造vue項(xiàng)目的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-10-10