写在开头
终极解决方法,直接看文章 解决循环跳转页面不刷新 部分
浏览器前进刷新后退不刷新
这一部分用来祭奠我曾经掉落在地的几根头发和逝去的时间。
-
思路:
-
vue
页面浏览记录后退的能力是通过插件vue-router
实现的。而vue-rotuer
通过history.pushState
方法增加浏览记录(或者history.replaceState
),然后通过监听popstate
事件来监听浏览器当前会话页面后退行为。 -
vue-rotuer
调用history.pushState
的时候会传入一个{key: _key /*唯一值*/}
对象,而捕获popstate
事件时,可以通过e.state.key
获取这个唯一值 - 基于以上两点,我们可以通过收集 key 值来组成页面之间的跳转记录,然后通过
popstate
事件的key值来判断页面是前进还是后退。页面前进时,直接使用keep-alive
组件缓存当前页面;如果页面后退,那么在回到上一个页面之前从keep-alive
中清除当前页面缓存
-
-
实现代码
<!-- 组件 keep-previous-alive 代码 --> <template> <keep-alive> <slot /> </keep-alive> </template> <script> let routerHistory = []; function remove(arr, item) { if (arr.length) { const index = arr.indexOf(item); if (index > -1) { return arr.splice(index, 1); } } } // 清除 keep-alive 中的缓存 function pruneCacheEntry(cache, key, keys) { const cached = cache[key]; if (cached) { cached.componentInstance.$destroy(); } cache[key] = null; remove(keys, key); } function handleAfterEachRouter(to, from) { const historyState = window.history.state || {}; const historyStateKey = historyState.key; const index = routerHistory.indexOf(historyStateKey); if (index > -1) { // 回到旧页面 routerHistory.splice(index + 1); } else { // 进入新页面 routerHistory.push(historyStateKey); } } function handleBeforeEachRouter(to, from, next) { // setTimeout 确保 handlePopstate 方法的调用是在进入下个页面之前 setTimeout(() => { next(); }, 0); } function setup(com) { const router = com.$router; if (router.beforeHooks.indexOf(handleBeforeEachRouter) <= -1) { router.beforeEach(handleBeforeEachRouter); } if (router.afterHooks.indexOf(handleAfterEachRouter) <= -1) { router.afterEach(handleAfterEachRouter); } } export default { name: 'keep-previous-alive', components: {}, props: {}, data() { return { keepAliveVnode: null, }; }, methods: { handlePopstate(e) { const state = e.state || {}; const stateKey = state.key; const vnode = this.$slots.default && this.$slots.default[0]; if (!vnode) return; if (stateKey === undefined) return; if (routerHistory.indexOf(stateKey) <= -1) return; // history back const keepAliveInstance = this.keepAliveVnode.componentInstance; const componentOptions = vnode.componentOptions; const key = vnode.key == null ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : "") : vnode.key; pruneCacheEntry(keepAliveInstance.cache, key, keepAliveInstance.keys); }, }, created() { window.addEventListener("popstate", this.handlePopstate); window.vm = this; }, mounted() { this.keepAliveVnode = this._vnode; setup(this); }, beforeDestroy() { window.removeEventListener("popstate", this.handlePopstate); }, }; </script> <style lang="scss" scoped> </style>
-
使用方法
<template> <div> <KeepPreviousAlive> <router-view v-if="$route.matched.some((r) => r.meta.keepAlive)"></router-view> </KeepPreviousAlive> <router-view v-if="!$route.matched.some((r) => r.meta.keepAlive)"></router-view> </div> </template>
-
优点和缺陷
- 配置简单:只要使用
KeepPreviousAlive
组件就可以,不用在缓存页面的beforeRouteEnter
守卫方法中判断是否需要重新创建页面。 - 缺点:存在循环跳转时,获取到页面数据都是缓存中的,不会重新刷新页面。例如:
- 用户在 A 页面,调用
router.push('/b')
跳转到 B 页面 - 用户已经跳到 B 页面,然后调用
router.push('/a')
跳转到 A 页面 - 这时候 A 页面的数据还是从缓存中取到,并不会进入创建页面的流程。
- 用户在 A 页面,调用
- 配置简单:只要使用
<h3 id="3">解决循环跳转页面不刷新</h3>
在介绍解决方法之前,让我们一起忘掉上一部分关于 KeepPreviousAlive
组件的实现,来看下面一段代码。
// keep-alive 部分源码
const key = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : "")
: vnode.key;
pruneCacheEntry(keepAliveInstance.cache, key, keepAliveInstance.keys);
从上面的代码中可以知道 keep-alive
缓存的 key 值可以从 vnode.key
中获取,而 vnode.key
是通过 v-bind:key="xxx"
传入的,因此我们可以自己控制组件缓存的 key 值,那么当我们需要让某个已经缓存的组件重新进入创建页面的时候,可以改变这个组件的 key 值,让它命中不了缓存。
代码示例:
<template>
<div>
<!-- 给缓存设置最大个数,避免占用太多内存 -->
<keep-alive :max="20">
<!-- 传入缓存 key 值 -->
<router-view :key="$route.fullPath" v-if="$route.matched.some((r) => r.meta.keepAlive)" />
</keep-alive>
<router-view v-if="!$route.matched.some((r) => r.meta.keepAlive)" />
</div>
</template>
// 其他页面
// 进入a页面时,a页面需要重新创建
this.$router.push({
path: '/a',
query: {
q: 'random querystring'
}
})