效果图
默认情况下没有 noCache属性 或为false 都会进行缓存 true不缓存
image.png
image.png
image.png
再切回来input内容还在
image.png
如果为true
image.png
image.png
image.png
再切回来 input内容没有了
image.png

右键删除某一个标签导航时也要删除对应路由缓存
效果
image.png
关闭后再打开 input内容被清空

image.png

5-1 修改store创建缓存列表

再添加标签导航时,同时也判断该路由要不要缓存,要缓存就根据路由配置的name属性进行缓存(路由组件的name要与路由配置的name一致)再添加到keep-alive inludes的缓存列表中。 (keep-alive内部是根据组件的name进行缓存,我们添加到cachedViews缓存列表的name是从每条路由配置的name取得值,所以路由组件和路由配置中必须要有一致的name属性。)

tagsView module

主要就是添加cachedViews缓存集合,再新增用来添加和删除cachedViews缓存列表actions和muations

state
image.png
image.png
actions (添加、删除)
image.png
mutations
image.png
src/store/modules/tagsView.ts

  1. import { Module, ActionTree, MutationTree } from 'vuex'
  2. import { RouteRecordRaw, RouteRecordNormalized, RouteRecordName } from 'vue-router'
  3. import { IRootState } from '@/store'
  4. // 携带fullPath
  5. export interface RouteLocationWithFullPath extends RouteRecordNormalized {
  6. fullPath?: string;
  7. }
  8. export interface ITagsViewState {
  9. // 存放当前显示的tags view集合
  10. visitedViews: RouteLocationWithFullPath[];
  11. // 根据路由name缓存集合
  12. cachedViews: RouteRecordName[];
  13. }
  14. // 定义mutations
  15. const mutations: MutationTree<ITagsViewState> = {
  16. // 添加可显示tags view
  17. ADD_VISITED_VIEW(state, view) {
  18. // 过滤去重
  19. if (state.visitedViews.some(v => v.path === view.path)) return
  20. // 没有titles时处理
  21. state.visitedViews.push(Object.assign({}, view, {
  22. title: view.meta.title || 'tag-name'
  23. }))
  24. },
  25. // 如果路由meta.noCache没有 默认或为false代表进行缓存,为true不缓存
  26. // 默认缓存所有路由
  27. ADD_CACHED_VIEW(state, view) {
  28. // 只有路由有name才可缓存集合keep-alive inludes使用
  29. if (state.cachedViews.includes(view.name)) return
  30. if (!view.meta.noCache) {
  31. state.cachedViews.push(view.name)
  32. }
  33. },
  34. // 可删除指定的一个view
  35. DEL_VISITED_VIEW(state, view) {
  36. const i = state.visitedViews.indexOf(view)
  37. if (i > -1) {
  38. state.visitedViews.splice(i, 1)
  39. }
  40. },
  41. // 可删除指定的一个view缓存
  42. DEL_CACHED_VIEW(state, view) {
  43. const index = state.cachedViews.indexOf(view.name)
  44. index > -1 && state.cachedViews.splice(index, 1)
  45. },
  46. // 清空缓存列表
  47. DEL_ALL_CACHED_VIEWS(state) {
  48. state.cachedViews = []
  49. }
  50. }
  51. // 定义actions
  52. const actions: ActionTree<ITagsViewState, IRootState> = {
  53. // 添加tags view
  54. addView({ dispatch }, view: RouteRecordRaw) {
  55. // 添加tag时也要判断该tag是否需要缓存
  56. dispatch('addVisitedView', view)
  57. dispatch('addCachedView', view)
  58. },
  59. // 添加可显示的tags view 添加前commit里需要进行去重过滤
  60. addVisitedView({ commit }, view: RouteRecordRaw) {
  61. commit('ADD_VISITED_VIEW', view)
  62. },
  63. // 添加可缓存的标签tag
  64. addCachedView({ commit }, view: RouteRecordRaw) {
  65. commit('ADD_CACHED_VIEW', view)
  66. },
  67. // 删除指定tags view 同时要把它从visitedViews和cachedViews中删除
  68. delView({ dispatch }, view: RouteRecordRaw) {
  69. return new Promise(resolve => {
  70. // 删除对应显示的路由tag
  71. dispatch('delVisitedView', view)
  72. // 删除对应缓存的路由
  73. dispatch('delCachedView', view)
  74. resolve(null)
  75. })
  76. },
  77. // 从可显示的集合中 删除tags view
  78. delVisitedView({ commit }, view: RouteRecordRaw) {
  79. commit('DEL_VISITED_VIEW', view)
  80. },
  81. // 从缓存列表删除指定tag view
  82. delCachedView({ commit }, view: RouteRecordRaw) {
  83. return new Promise(resolve => {
  84. commit('DEL_CACHED_VIEW', view)
  85. resolve(null)
  86. })
  87. },
  88. // 清空缓存列表
  89. delAllCachedViews({ commit }) {
  90. commit('DEL_ALL_CACHED_VIEWS')
  91. }
  92. }
  93. const tagsView: Module<ITagsViewState, IRootState> = {
  94. namespaced: true,
  95. state: {
  96. visitedViews: [],
  97. cachedViews: []
  98. },
  99. mutations,
  100. actions
  101. }
  102. export default tagsView

5-2 AppMain中根据store中cachedViews列表进行缓存

之前我们是在AppMian中给了keep-alive的includes空数组,现在换成从store中获取cachedViews缓存列表

image.png
src/layout/components/AppMain.vue

  1. <template>
  2. <div class="app-main">
  3. <!-- vue3 路由缓存 https://next.router.vuejs.org/guide/migration/index.html#router-view-keep-alive-and-transition -->
  4. <router-view v-slot={Component}>
  5. <transition name="fade-transform" mode="out-in">
  6. <keep-alive :include="cachedViews">
  7. <component :is="Component" :key="key" />
  8. </keep-alive>
  9. </transition>
  10. </router-view>
  11. </div>
  12. </template>
  13. <script lang="ts">
  14. import { computed, defineComponent } from 'vue'
  15. import { useRoute } from 'vue-router'
  16. import { useStore } from '@/store'
  17. export default defineComponent({
  18. name: 'AppMain',
  19. setup() {
  20. const route = useRoute()
  21. const store = useStore()
  22. const key = computed(() => route.path)
  23. // 缓存路由集合 暂时先是空数组
  24. const cachedViews = computed(() => store.state.tagsView.cachedViews)
  25. return {
  26. key,
  27. cachedViews
  28. }
  29. }
  30. })
  31. </script>
  32. <style lang="scss" scoped>
  33. .app-main {
  34. /* navbar 50px */
  35. min-height: calc(100vh - 50px);
  36. }
  37. .fade-transform-enter-active,
  38. .fade-transform-leave-active {
  39. transition: all .5s;
  40. }
  41. .fade-transform-enter-from {
  42. opacity: 0;
  43. transform: translateX(-30px);
  44. }
  45. .fade-transform-leave-to {
  46. opacity: 0;
  47. transform: translateX(30px);
  48. }
  49. </style>

5-3 修改SizeSelect组件

SizeSelect组件主要用来切换element组件size,由于路由默认会被缓存,会导致动态修改了elemnt组件size不更新,所以需要在修改size后 清空缓存里列表 在刷新当前路由

image.png

  1. <template>
  2. <div>
  3. <el-dropdown trigger="click" @command="handleSize">
  4. <div>
  5. <svg-icon class-name="size-icon" icon-class="size"></svg-icon>
  6. </div>
  7. <template #dropdown>
  8. <el-dropdown-menu>
  9. <el-dropdown-item
  10. v-for="item in sizeOptions"
  11. :key="item.value"
  12. :command="item.value"
  13. :disabled="item.value === size"
  14. >
  15. {{ item.label }}
  16. </el-dropdown-item>
  17. </el-dropdown-menu>
  18. </template>
  19. </el-dropdown>
  20. </div>
  21. </template>
  22. <script lang="ts">
  23. import { Size } from '@/plugins/element'
  24. import {
  25. defineComponent,
  26. ref,
  27. getCurrentInstance,
  28. ComponentInternalInstance,
  29. ComponentPublicInstance,
  30. computed
  31. } from 'vue'
  32. import { useRoute, useRouter } from 'vue-router'
  33. import { useStore } from '@/store'
  34. import { nextTick } from 'process'
  35. export default defineComponent({
  36. name: 'SizeSelect',
  37. setup() {
  38. const store = useStore()
  39. const route = useRoute()
  40. const router = useRouter()
  41. const { proxy } = getCurrentInstance() as ComponentInternalInstance
  42. // store中获取size
  43. const size = computed(() => store.getters.size)
  44. // element size 选项
  45. const sizeOptions = ref([
  46. { label: 'Default', value: 'default' },
  47. { label: 'Medium', value: 'medium' },
  48. { label: 'Small', value: 'small' },
  49. { label: 'Mini', value: 'mini' }
  50. ])
  51. // 刷新当前路由
  52. const refreshView = () => {
  53. // 需要清除路由缓存 否则size配置改变后组件size状态被缓存不更新
  54. store.dispatch('tagsView/delAllCachedViews')
  55. const { fullPath } = route
  56. nextTick(() => {
  57. // 跳转到重定向中间页 实现当前路由刷新
  58. router.replace({
  59. path: '/redirect' + fullPath
  60. })
  61. })
  62. }
  63. // command 获取点击按钮的command属性值 作为size值
  64. const handleSize = (command: Size) => {
  65. // 修改element-plus组件尺寸
  66. (proxy as ComponentPublicInstance).$ELEMENT.size = command
  67. // 更新store
  68. store.dispatch('app/setSize', command)
  69. // 切换size需要刷新路由才能生效
  70. refreshView()
  71. proxy?.$message.success({
  72. type: 'success',
  73. message: 'Switch Size Success'
  74. })
  75. }
  76. return {
  77. sizeOptions,
  78. size,
  79. handleSize
  80. }
  81. }
  82. })
  83. </script>
  84. <style lang="scss">
  85. .size-icon {
  86. font-size: 18px;
  87. }
  88. </style>

5-3 测试

image.png
通过修改路由 noCache属性来测试
image.png

本节参考源码

https://gitee.com/brolly/vue3-element-admin/commit/cb38b128c2a0b193d4ca42083b11e1c1b1c97870