vue單頁應用中 返回列表記住上次滾動位置、keep-alive快取之後更新列表資料 那點事

vbyzc發表於2018-11-13

實踐場景需求

  • 產品列表中,滾動到一定位置的時候,點選檢視產品資訊,後退之後,需要回到原先的滾動位置,這是常見的需求
  • 所有頁面均在router-view中,暫時使用了keep-alive來快取所有頁面,所以進入不同分類的產品列表,和不同的產品詳情頁面,需要更新資料

首先注意:

  • 本次實踐測試環境為pc端的webkit核心瀏覽器,手機暫時不測試
  • 使用$router.back(-1) 和瀏覽器後退按鈕效果一樣
  • 必須使用keep-alive快取路由頁面才能記住上次的位置,否則使用瀏覽器後退或$router.back後退都會重新載入資料,使原來的內容高度改變
  • 使用keep-alive之後,路由頁面才能使用activated事件

記錄上次列表滾動的高度位置,後退時恢復

思路很簡章,在路由元資訊中設定一個變數:scrollToTop,即標記是否要回到頂部,而我們的產品頁面productList是要恢復上次滾動高度的,不回到頂部,所以設定為false
當離開路由的時候,還是判斷這個變數是否為false,是則記錄滾動的高度到vuex中 (所以我們這個變數有2個作用,你要維護2個也可以)
然後每當進入路由頁面的時候,如果本路由的scrollToTop為false,則從vuex中讀取上次記錄的高度,並恢復
首先要注意一點,vue單頁應用,切換路由時,滾動高度不會變,因為切換路由時只是切換了頁面的內容,與滾動高度
當你在某路由頁面的時候,滾動了100畫素,然後切換了新的路由頁面(改變了頁面中的內容),只要新的路由頁也可以滾動出100畫素, 後退時,滾動高度不變, 這可以說是"BUG"
所以下面程式碼我們預設把 scrollToTop為true的設定滾動高度為0


我路由中的配置:

routes: [{
            path: '/',
            name: 'home',
            component: Home,
            meta: { title: "鳳凰旅遊" ,scrollToTop:true}
        },
        {
            path: '/product',
            name: 'product',
            component: () => import('./views/Product.vue'),
            meta: { title: "旅遊" ,scrollToTop:true}
        },
        {
            path: '/productList/:id',
            name: 'productList',
            component: () => import('./views/productList.vue'),
            meta: { title: "列表" ,scrollToTop:false}
        },
        {
            path: '/productShow/:id',
            name: 'productShow',
            component: () => import('./views/productShow.vue'),
            meta: { title: "旅遊產品顯示" ,scrollToTop:true}
        }
    ]複製程式碼

接下來在store.js中,維護一個變數用於記錄

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    scrollTop :0
  },
  mutations: {
      recordScrollTop(state,n){
          state.scrollTop = n
      }
  },
  actions: {

  }
})複製程式碼

在main.js中的相關路由事件裡

router.beforeEach(function(to,from,next){
    document.title = to.meta.title
    // 要離開頁面如果設定為不滾回到頂部,則本頁是要記住上滾動高度到vuex中,以便下次進來恢復高度
    if(from.meta.scrollToTop==false) {
        store.commit('recordScrollTop', document.documentElement.scrollTop)
    }
    next()
})
router.afterEach((to, from) => {

    // 如果進入後的頁面是要滾動到頂部,則設定scrollTop = 0
    //否則從vuex中讀取上次離開本頁面記住的高度,恢復它
    if(to.meta.scrollToTop==true){
        setTimeout(()=>{
            document.documentElement.scrollTop = 0
        },10)
    }else{
        setTimeout(()=>{
            document.documentElement.scrollTop = store.state.scrollTop
        },50)

    }
});複製程式碼
/*
*  讀取上次記錄的滾動高度並且設定,必須放在router.afterEach裡面
*  由於快取了產品列表頁面,每次進入會判斷如果進入的是否為新的類別,則清空資料再重新載入資料,這時想設定scrollTop就無效,應該是檢視渲染更新是非同步的原因
*  看官方文件說呼叫afterEach之後,才觸發 DOM 更新
*  所以afterEach裡面的有涉及到DOM的操作,放在setTimeout裡面,否則我試出了亂子..
*/複製程式碼

使用了keep-alive快取了頁面之後,當引數不同的時候,更新資料

當進入不同分類的產品列表頁面,或不同id的產品頁面,由於快取了上次的結果,當然要我們來處理更新
首先上面有說到,使用了keep-alive,路由頁面便可以使用activated事件,因為使用了keep-alive其它普通的生命週期只執行了一次,而activated每次顯示頁面都會啟用(類似小程式的onShow),必須使用這個來更新
思路:在頁面data中維護一個id,預設為0 ,每次進這個頁面時呼叫activated事件,在事件時面,判斷這個id是否與路由url中的引數一致
如果不一致,則根據這個id更新相關資料,並且 把data中的id,更新為新的id ,別忘了還要清空上次分類的產品資料
當然別忘了,你應該初始讀取一次資料,比如在created裡面, 而activated第一次建立頁面時不會啟用,快取之後,第二次進入才會開始啟用(我想應該是如此。。)

上程式碼:

activated(){
        //由於快取了本頁面,每次啟用頁面都要判斷是否重置相關引數,並重新載入資料
        if(this.id !== this.$route.params.id){this.id = this.$route.params.id //更新分類id
            this.curpage = 1 //初始化頁面為1
            this.product = [] //清空上次不同分類的產品資料
            this.getProduct('/api/productList.php',this.id,this.curpage).then((res)=>{
                this.ptotal = res.total
                res.products.forEach((item)=>{
                    this.product.push (item)
                })
                this.loading = false
            })
        }
    }複製程式碼

如果更新或更正再補充。


相關文章