updateRoute (route: Route) { const prev = this.current this.current = route this.cb && this.cb(route) this.router.afterHooks.forEach(hook => { hook && hook(route, prev) }) }
在这里虽然 afterHooks 的执行是晚于路由的设置的,但组件的 render 是在 nextTick 中执行的,也就是说,在keep-alive的render方法判断是否应当从缓存中获取组件时,组件的保活状态已经被我们修改了。
劫持router.push
这里我们将劫持router的push方法:
let dir = 1; const includes = []; const routerPush = router.push; router.push = function push(...args) { dir = 1; routerPush.apply(router, args); }; router.afterEach((to, from) => { if (dir === 1) { includes.push(to.name); } else if (dir === -1) { includes.pop(); } dir = -1; });
我们将router.push(当然这里需要劫持的方法不止是push,在此仅用push作为示例)和浏览器的回退行为用不同的 dir 标记,并根据这个值来维护includes数组。
然后,将includes传递给keep-alive组件:
// html <div> <keep-alive :include="includes"> <router-view></router-view> </keep-alive> </div> // js const app = new Vue({ router, data() { return { includes, }; }, }).$mount('#app');
维护滚动
接下来,我们将编写一个 keep-position 指令(directive):
Vue.directive('keep-position', { bind(el, { value }) { const parent = positions[positions.length - 1]; const obj = { x: 0, y: 0, }; const key = value; parent[key] = obj; obj.el = el; obj.handler = function ({ currentTarget }) { obj.x = currentTarget.scrollLeft; obj.y = currentTarget.scrollTop; }; el.addEventListener('scroll', obj.handler); }, });
并对router进行修改,来维护position数组:
const positions = []; router.afterEach((to, from) => { if (dir === 1) { includes.push(to.name); positions.push({}); } ... });
起初我想通过指令来移除事件侦听(unbind)以及恢复滚动位置,但发现使用unbind并不方便,更重要的是指令的几个生命周期在路由跳转到保活的页面时都不会触发。
因此这里我还是使用 afterEach 来处理路由维护,这样在支持回退多步的时候也比较容易去扩展:
router.afterEach((to, from) => { if (dir === 1) { includes.push(to.name); positions.push({}); } else if (dir === -1) { includes.pop(); unkeepPosition(positions.pop({})); restorePosition(); } dir = -1; }); const restorePosition = function () { Vue.nextTick(() => { const parent = positions[positions.length - 1]; for (let key in parent) { const { el, x, y } = parent[key]; el.scrollLeft = x; el.scrollTop = y; } }); }; const unkeepPosition = function (parent) { for (let key in parent) { const obj = parent[key]; obj.el.removeEventListener('scroll', obj.handler); } };
最后,我们分别给我们的列表加上我们的指令就可以了:
<div v-keep-position="'list1'"> <!-- --> </div> <div v-keep-position="'list2'"> <!-- --> </div>