需求:在一個vue的項目中,咱們須要從一個列表頁面點擊列表中的某一個詳情頁面,從詳情頁面返回不刷新列表,而從列表的上一個頁面從新進入列表頁面則須要刷新列表。vue
而瀏覽器的機制則是每一次的頁面打開都會從新執行全部的程序,因此這個功能並不能直接實現。而vue-router給咱們提供了一個叫scrollBehavior的回調函數,我門能夠用這個方法結合keep-alive能很好的實現這個功能,下面第一步附上實現代碼:vue-router
首先咱們建立a,b,c,d四個頁面,在路由的meta屬性中添加須要緩存的頁面標識(isKeepAlive):瀏覽器
import Vue from 'vue' import Router from 'vue-router' const HelloWorld = () => import('@/components/HelloWorld') const A = () => import('@/components/router-return/router-a') const B = () => import('@/components/router-return/router-b') const C = () => import('@/components/router-return/router-c') const D = () => import('@/components/router-return/router-d') Vue.use(Router) const routes = [ { path: '/', name: 'HelloWorld', component: HelloWorld }, { path: '/a', name: 'A', component: A }, { path: '/b', name: 'B', component: B, meta: { isKeepAlive: true } }, { path: '/c', name: 'C', component: C }, { path: '/d', name: 'D', component: D } ]
而後咱們修改app.vue頁面:緩存
<template> <div id="app"> <img src="./assets/logo.png"> <keep-alive> <router-view v-if="$route.meta.isKeepAlive"/> </keep-alive> <router-view v-if="!$route.meta.isKeepAlive"/> </div> </template>
最後咱們添加new Router方法的scrollBehavior的回調處理方法:app
export default new Router({ routes, scrollBehavior (to, from, savedPosition) { // 從第二頁返回首頁時savedPosition爲undefined if (savedPosition || typeof savedPosition === 'undefined') { // 只處理設置了路由元信息的組件 from.meta.isKeepAlive = typeof from.meta.isKeepAlive === 'undefined' ? undefined : false to.meta.isKeepAlive = typeof to.meta.isKeepAlive === 'undefined' ? undefined : true if (savedPosition) { return savedPosition } } else { from.meta.isKeepAlive = typeof from.meta.isKeepAlive === 'undefined' ? undefined : true to.meta.isKeepAlive = typeof to.meta.isKeepAlive === 'undefined' ? undefined : false } } })
在scrollBehavior方法中的savedPosition參數,每一次點擊進去的值爲null,而點擊瀏覽器的前進與後退則會返回上一次該頁面離開時候的pageXOffset與pageYOffset的值,而後咱們能夠根據這個返回的值來修改路由信息裏面的isKeepAlive值來控制是否顯示緩存。函數
咱們來看下vue-router裏面scrollBehavior執行的源碼:url
在vue-router.js的1547行發現:spa
function handleScroll ( router, to, from, isPop) { if (!router.app) { return } var behavior = router.options.scrollBehavior; if (!behavior) { return } { assert(typeof behavior === 'function', "scrollBehavior must be a function"); } // wait until re-render finishes before scrolling router.app.$nextTick(function () { // 獲得該頁面以前的position值,若是沒有緩存則返回null var position = getScrollPosition(); var shouldScroll = behavior(to, from, isPop ? position : null); if (!shouldScroll) { return } if (typeof shouldScroll.then === 'function') { shouldScroll.then(function (shouldScroll) { // 移動頁面到指定位置 scrollToPosition((shouldScroll), position); }).catch(function (err) { { assert(false, err.toString()); } }); } else { // 移動頁面到指定位置 scrollToPosition(shouldScroll, position); } }); }
再看下上面方法中用到的幾個主要方法的寫法:code
// getScrollPosition 獲得移動的座標 function getScrollPosition () { var key = getStateKey(); if (key) { return positionStore[key] } } // scrollToPosition 頁面移動方法 function scrollToPosition (shouldScroll, position) { var isObject = typeof shouldScroll === 'object'; if (isObject && typeof shouldScroll.selector === 'string') { var el = document.querySelector(shouldScroll.selector); if (el) { var offset = shouldScroll.offset && typeof shouldScroll.offset === 'object' ? shouldScroll.offset : {}; offset = normalizeOffset(offset); position = getElementPosition(el, offset); } else if (isValidPosition(shouldScroll)) { position = normalizePosition(shouldScroll); } } else if (isObject && isValidPosition(shouldScroll)) { position = normalizePosition(shouldScroll); } if (position) { window.scrollTo(position.x, position.y); } }
而後咱們看看vue-router是怎麼緩存頁面x,y的座標的,上面的getScrollPosition是用來獲取座標的,那麼確定也有保存座標的方法,在getScrollPosition的上面一個方法則是saveScrollPosition就是保存的方法:component
// saveScrollPosition function saveScrollPosition () { var key = getStateKey(); if (key) { positionStore[key] = { x: window.pageXOffset, y: window.pageYOffset }; } }
而這個保存的方法會有一個key值是緩存的標識,繼續查找getStateKey:
根據上面代碼發現key值就是一個時間值。而setStateKey則是一個key值更新的方法,而後繼續查找setStateKey執行的地方:
function setupScroll () { // Fix for #1585 for Firefox window.history.replaceState({ key: getStateKey() }, ''); window.addEventListener('popstate', function (e) { saveScrollPosition(); if (e.state && e.state.key) { setStateKey(e.state.key); } }); }
而後發現該方法執行的地方是popState執行的時候,而key的來源則是popState返回參數裏面的state屬性裏面,而state值的設定則是pushstate執行的時候傳進去的,因此咱們繼續查pushstate執行的方法:
function pushState (url, replace) { saveScrollPosition(); // try...catch the pushState call to get around Safari // DOM Exception 18 where it limits to 100 pushState calls var history = window.history; try { if (replace) { history.replaceState({ key: _key }, '', url); } else { _key = genKey(); history.pushState({ key: _key }, '', url); } } catch (e) { window.location[replace ? 'replace' : 'assign'](url); } }
根據上面代碼發現,每次push的時候都會去生成一個當前時間的key值保存在state裏面,做用於popstate時使用。
那麼到此scrollBehavior方法的整個執行邏輯就清楚了:該方法最主要的是運用了瀏覽器的popstate方法只會在瀏覽器回退與前進纔會執行的機制,在頁面進入時生成一個惟一的key值保存在state裏面,離開的時候將頁面滾動位置保存在state裏面的惟一key值上。每次pushstate的時候key值都是最新的,沒有緩存因此返回null,而執行popstate的時候state裏面的key都有緩存,則返回上次離開時候的滾動座標。