零、前言
最近參與了一個立足 seo 的移動端項目,公司前端工程主棧 vue,所以理所當然的用上了 nuxt,UI 主要選擇了 Vant。
一、公共列表頁的緩存
公共列表頁由於數據量較大,故需要滾動觸發分次加載以模擬分頁,當客戶端加載到了一定頁數之后(>= 2),點擊某條數據進去查看詳情,然后返回列表頁,這時候如果沒有緩存的話,列表頁會按照初始參數(即從分頁 1 開始)發請求拉數據,並且丟失瀏覽位置。一方面,浪費請求資源,另一方面,用戶體驗不佳。因此,針對這兩個問題,最后決定開啟緩存。
vue 中的緩存直接使用 <keep-live> 組件即可,配合上 vue-router 中的 scrollBehavior 往往能比較容易地實現。當然這里容易有忽略:“scrollBehavior 只在支持 history.pushState 的瀏覽器中可用。” 接受三個參數,to, from, savedPosition, 而 savedPosition “當且僅當 popstate 導航 (通過瀏覽器的 前進/后退 按鈕觸發) 時才可用。” 這意味這個參數着只有 this.$router.go(...) 有效,this.$router.push() 時是無效的!並且,我們在使用 <keep-live> 時,通常會在自定義的路由中添加某些字段,但 nuxt 的路由是自動生成的,這里是最大的限制。
這里有一個前置,在 nuxt.js 的 1.x 某個更新中, <nuxt> 和 <nuxtChild> 組件添加了 keepAlive 屬性。於是我們可以在 default.vue 中定制這個屬性:
<nuxt :keep-alive='viewCache' :keepAliveProps="{include: includeArr}"/> data() { return { cache: false, includeArr: ['list-name-1', 'list-name-2'] // 組件的 name } } watch: { '$route': function(new, old) { if (...) { this.cache = true; } else { this.cache = false; } } }
然后,在跳轉進入列表頁的組件上綁定相應的參數,從而控制是否需要緩存,如:
this.$router.push({ name: 'list-name-1', params: { cache: true } })
最后,綁定 keepAliveProps 是為了避免多余組件的緩存.
另外,從列表頁跳轉詳情頁的時候,也需要加上控制字段,如:
this.$router.push({ name: 'details-name-1', params: { details_id: xxxxx, cache: true } })
並且,從詳情頁返回時,也需要加上控制字段,如
this.$router.push({ name: 'list-name-1', params: { cache: true } })
至此,可以解決請求浪費,列表數據緩存的問題,同時,缺點也比較明顯,需要在父組件/列表頁/詳情頁同時添加控制字段才能實現,耦合度較高,並且,用戶在列表頁刷新一下即失效。
其次,對於滾動條位置記錄,在多番嘗試之后,也沒有一個較便捷的方法。原因除上文中提到的 savedPosition 的限制以外,與列表數據的緩存也有關系:整個頁面的高度需要由數據撐開,然后才能進行滾動。下面是我的解決方案,依賴上文中的組件(數據)緩存:
由於列表頁不進行 ssr, 故在使用 keepAlive 時同時可以使用 vue 的 activated/deactivated 這兩個生命周期:
activated() { setTimeout(() => { window.scrollTo(0, this.scrollPosition); // window.scrollTo({ // top: this.scrollPosition, // behavior: "smooth" // }); }, 0) }, deactivated(){ this.scrollPosition = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; }
這里需要注意的是要使用 setTimeout (或者this.$nextTick(),不過我嘗試的時候沒有效果),否則不會滾動。
當然,這里也存在一定的問題, window.scrollTo(x, y) 動作是瞬間完成的,因此可能會存在一定的閃爍情況,而使用 window.scrollTo({..., behavior: "smooth" }) 則在每次用戶返回的時候都會有個明顯的下滑動作,所以,如何取舍,應當依據實際情況。
附上對列表數據緩存的實踐和資料:
* (未嘗試) 使用服務器緩存 - https://juejin.im/post/5b2b62096fb9a00e61494b0b
* ✗ 使用 van-popup/dialog 組件 - 但是會引起 asyncData 中的傳值收值
* ✗ this.$router.go 函數 - 無法緩存
* ✗ 使用 $store + van-popup/dialog,_details 是頁面級組件,如果使用 import 導入將不會觸發 asyncData 方法,從而導致某些依賴性數據為 undefinded, 最終在 rendering 過程報錯。
二、動態路由
同一個文件夾下不應該存在兩個及以上的 _xxx.vue (動態)組件。
三、生命周期
nuxt.js 的服務端渲染過程基於 node,所以其某些周期是運行在服務端的,在引入第三方插件,或者直接在代碼中使用 window 和 document 時,控制台會給出警告:window 未定義。
解決辦法有兩種:
1、使用 process.browser 來區分環境,如:
if (process.browser) { // 修改window對象下某一屬性 window.mbk = ... }
2、在 mounted 以及之后的周期中使用;