vue-router中scrollBehavior的妙用

卸帳篷的黑人發表於2018-07-06

1. keep-alive

  • 問題: 使用keep-alive標籤後部分安卓機返回快取頁位置不精確問題

  • 解決方案:

<div id="app">
    <keep-alive>
      <router-view v-if="$route.meta.keepAlive"></router-view>
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
複製程式碼
const router = new Router({
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition && to.meta.keepAlive) {
      return savedPosition;
    }
    return { x: 0, y:0 };
  },
});
複製程式碼

2. 頁面返回出現空白屏問題

  • 問題
【前提】:iOS裝置
【步驟】: 頁面A是個列表很長-->滑到頁尾的時候點選跳轉之後到頁面B--->再返回A頁面
         --->螢幕會出現空白遮罩層--->手指輕觸螢幕滑動--->遮罩層消失
複製程式碼

問題圖片

解決方案一

在介面請求成功後的回撥操作完成後進行該操作,例如

// fetchCourseList是一個封裝好的Promise請求
fetchCourseList().then(({ data: courses }) => {
  this.courses = courses;
}).then(() => {
    setTimeout(() => {
        window.scrollTo(0, 1);
        window.scrollTo(0, 0);
    });
});
複製程式碼

該方案的弊端: 每個頁面都需要做這樣的處理,不推薦使用。

解決方案二(推薦)

使用scrollBehavior中的非同步滾動操作

const router = new Router({
  scrollBehavior(to, from, savedPosition) {
    // keep-alive 返回快取頁面後記錄瀏覽位置
    if (savedPosition && to.meta.keepAlive) {
      return savedPosition;
    }
    // 非同步滾動操作
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({ x: 0, y: 1 });
      }, 0);
    });
  },
});
複製程式碼

該方案直接在路由進行處理,相容每個頁面並且頁面載入完後並也不會產生1px的滾動位置。

這裡為什麼不能直接return而必須使用非同步滾動操作呢?以下是個人的一些見解歡迎大家來探討指正。

  1. 首先我們要先去了解scrollBehavior函式究竟在元件的哪個生命週期後才開始執行。這裡我對元件的每個生命週期和scrollBehavior函式進行alert,經排查結果:scrollBehavior函式在元件的生命週期mounted後beforeUpdate前執行。

  2. 在scrollBehavior函式中直接return{ x:0, y:100},進入頁面仍在頂部。為什麼不會滾動到100px處?猜測:mounted中的非同步請求回來的資料賦值給data中的變數a,變數a因為vue的雙向繫結更新了view層而引起滾動失效?

  3. 驗證下以上的猜測,設定一個靜態頁面資料都已經在html上寫死。scrollBehavior函式中直接return { x:0, y: 100},結果:進入頁面都會滾動到100px處。證明:確實與非同步請求回來後的操作有關係。

  4. 接著瞭解vue的mounted和beforeUpdate時期都做了些什麼。mounted時期:data資料已經掛在到頁面上。beforeUpdate和updated時期:當vue發現data中的資料發生了改變,會觸發對應元件的重新渲染。

  5. 根據步驟4.mounted時期發起的非同步請求並不會阻礙主執行緒的後續操作,所以請求回撥事件未觸發(對data中的變數a賦值操作未執行)便繼續去執行scrollBehavior函式。如果此時直接return{ x:0, y:100}。此時相當於在非同步請求回撥事件未執行前進行了滾動。等到滾動後非同步請求回撥事件開始執行,對data中的變數a被賦值,引起元件重新渲染又回到了頂部。這整個流程滾動是針對data的初始資料頁進行滾動的,所以遮罩層仍會出現。

  6. 綜合上述:必須使用非同步滾動,利用setTimeout跳出主執行緒將回撥事件放到佇列中。由於mouted比scrollBehavior函式早執行,所以非同步請求的回撥事件優先進入佇列,接下去才是setTimeout的回撥事件。根據佇列 先進先出的原理。先執行了非同步請求回撥事件對data中的變數a做賦值操作。此時相當於這已經是個靜態頁面了,接著我只要執行return { x:0, y: 100 }。這樣就已經觸發了頁面滾動到100px的效果。但是由於data資料發生改變,頁面重新渲染又回到頂部。這時整個輕觸滾動效果已經暗中執行完成,不會再出現遮罩層了。

相關文章