页面缓存根据需求分为以下3类
- 类小程序:根据用户的跳转方法来决定如何操作页面缓存堆栈
- 历史记录:根据用户访问的历史记录缓存一定数量的页面,超过限定数量时采用先进先出
- 面包屑:根据面包屑结构,缓存当前页面的祖先页面
类小程序
- 需要解决的问题
- 实现以下常用跳转方法
- navigateTo 保留当前页面,跳转到应用内的某个页面
- redirectTo 关闭当前页面,跳转到应用内的某个页面
- reLaunch 关闭所有页面,打开到应用内的某个页面
- navigateBack 关闭当前页面,返回上一页面或多级页面
- 以上跳转方式如何在 router-link 中实现
- 需要拦截 history、router 下对应的方法
- 需要拦截浏览器返回按钮
历史记录
面包屑
- App.vue
<template>
<div id="app">
<nav>
<router-link :to="{ name: 'order-form-list' }">list</router-link>
|
<router-link
:to="{ name: 'order-form-detail', params: { orderFormId: '1' } }"
>detail</router-link
>
|
<router-link
:to="{
name: 'order-form-logistics',
params: { orderFormId: '1', logisticsId: '1' },
}"
>logistics</router-link
>
</nav>
<keep-alive :include="keepAliveInclude">
<!-- router-view 是根据 key 和 include 去区分缓存的,key 可以用于区分同一个组件在不同路由下的缓存实例,auto-match-router-view见面包屑这篇笔记 -->
<auto-match-router-view :key="$route.fullPath"></auto-match-router-view>
</keep-alive>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import autoMatchRouterView from "./components/auto-match-router-view.vue";
export default defineComponent({
components: { autoMatchRouterView },
setup() {
return {
};
},
});
</script>
- pageCache.ts
- afterEach 守卫修改keep-alive的include不能实现缓存
import { compile } from 'path-to-regexp'
import { ref } from 'vue'
import type { RouteParams, RouteRecord, Router } from 'vue-router'
/**
* 获取父级路由
* @param arg
* @returns
*/
export function getFatherPageRoute(arg: {
record: RouteRecord
params: RouteParams
router: Router
}) {
const { record, params, router } = arg
const { path } = record
const toPath = compile(path)
const pagePath = toPath(params)
const pageRoute = router.resolve(pagePath)
return pageRoute
}
/**
* 获取keepAliveInclude
* @param arg
* @returns
*/
export function getKeepAliveInclude(arg: { router: Router }) {
const { router } = arg
let routeEach: ((value: unknown) => void) | undefined
const keepAliveInclude = ref<string[]>([])
router.beforeResolve(async (to, form, next) => {
routeEach = undefined
const { matched } = to
const toLastMatched = to.matched[to.matched.length - 1]
const toComponentNames: string[] = []
toLastMatched.components &&
Object.values(toLastMatched.components).forEach((component) => {
if (typeof component === 'function') return
if ('name' in component && component.name) {
toComponentNames.push(component.name)
}
})
const newKeepAliveInclude: string[] = []
for (let i = 0; i < matched.length - 1; i++) {
const match = matched[i]
const fatherPage = getFatherPageRoute({ record: match, params: to.params, router })
if (fatherPage.path === to.path) break
if (fatherPage.name && !fatherPage.meta?.noCache) {
const lastMatched = fatherPage.matched[fatherPage.matched.length - 1]
lastMatched.components &&
Object.values(lastMatched.components).forEach((component) => {
// 当组件未被加载过时import()引入的组件component===function
if (typeof component === 'function') return
if ('name' in component && component.name) {
newKeepAliveInclude.push(component.name)
}
})
}
}
if (keepAliveInclude.value.some((o) => toComponentNames.includes(o))) {
// 从子页面返回需要等待路由each后才能够改变keepAliveInclude,否则会清空页面实例
next()
await new Promise((resolve, reject) => {
routeEach = resolve
})
keepAliveInclude.value = newKeepAliveInclude
} else {
keepAliveInclude.value = newKeepAliveInclude
next()
}
})
router.afterEach(() => {
routeEach?.('')
})
return {
keepAliveInclude
}
}
- routes.ts
import { RouteConfig } from "vue-router";
import orderFormListVue from "./order-form-list.vue";
// 根据功能块划分页面集合,在 views 中建立对应的文件夹
// 下面的例子中,订单列表页 是 订单详情页 的父页面,订单详情页 是 订单物流信息页 的父页面
// 面包屑在导航时会存在父级页面的参数无从获取的问题,所以通过类似视图视图嵌套的写法实现路由嵌套,
// 使子页面的的路由包含父页面的路由,但是这只能解决通过 params 传递的参数,无法解决通过 query 传递的参数
// 在页面开发时,应当为通过 query 传递的参数设置默认值
// 例如:订单的物流信息页 通过 面包屑 跳转到 订单详情页 时可以拿到 orderFormId,因为它的 url 中已经包含了这个参数,
// 但是却无法拿到通过 query 传递的参数,比如:?status=edit(编辑)/see(查看)。一般建议在页面中给 status 初始值为 see 来解决。
const route: RouteConfig = {
// 每个功能块的根 path,和这个功能块的文件名相同,避免不同功能块之间冲突
path: "/order-form",
// 按照vue-touter的规范,含有children的视图嵌套,每一个层级都要有对应的组件
// 在router-view这个组件中会根据嵌套的层级去找到对应使用的组件
// 这里为了实现面包屑导航对router-view进行了改造,它将不再寻找相同层级的组件,而是会查找匹配路径中含有组件的下一个层级
// 比如:'/order-form/'匹配到了当前路由和 name:'order-form-list',本来 router-view 将按层级渲染当前路由中的组件,这将会造成错误
// 改造之后 auto-match-router-view 将向下一层匹配的路由寻找是否存在可以被渲染的组件,于是便匹配到了 order-form-detail.vue
children: [
// 订单列表页
{
path: "",
name: "order-form-list",
component: orderFormListVue,
meta: {
// 约定:面包屑的标题,只有含有这个标题的才会作为面包屑的一个层级
title: "订单列表页",
// 这个路径的组件不缓存,这里只是个示范
// 页面容器是指包含菜单、侧边栏、页面内容嵌入router-view 等组件的公共组件,常被用于路由的最外层
// 本人不建议这么使用,而是应该把页面容器作为一个公共组件在页面级组件中被引入和使用,这样更容易实现各页面使用插槽定制容器内的内容
// 这里提供 noCache 只是为了增加更多可能性
// noCache: true,
},
},
{
path: "detail/:orderFormId",
children: [
// 订单详情页
{
path: "",
name: "order-form-detail",
component: () =>
import(
/* webpackChunkName: "orderForm" */ "./order-form-detail.vue"
),
meta: {
title: "订单详情页",
},
},
// 订单的物流信息页
{
path: "logistics/:logisticsId",
name: "order-form-logistics",
component: () =>
import(
/* webpackChunkName: "orderForm" */ "./order-form-logistics.vue"
),
meta: {
title: "订单的物流信息页",
},
},
],
},
],
};
export default route;