vue3 页面缓存KeepAlive示例

3 阅读3分钟

KeepAlive 示例

1.全部缓存

//APP.vue
<router-view v-slot="{ Component }">
    <KeepAlive>
      <component :is="Component"/>
    </KeepAlive>
</router-view>

2.根据路由配置缓存

  • 通过v-if来实现、配置在路由即可
//router/index.js
const routes = [
  {
    path: '/',
    component: () => import('@/views/home/index.vue'),
    name: 'home',
    meta: {
      title: '首页',
      keepAlive: true,//是否缓存
    },
  },
]
const router = createRouter({
  history: createWebHashHistory(),
  routes,
})
//APP.vue
<template>
  <router-view v-slot="{ Component, route }">
    <keep-alive>
      <component :is="Component" :key="$route.fullPath" v-if="route.meta.keepAlive" />
    </keep-alive>
    <component :is="Component" :key="$route.fullPath" v-if="!route.meta.keepAlive" />
  </router-view>
</template>

3.动态控制页面缓存:include

  • 通过include匹配实现;
  • 要求:值要和页面组件的name一致;
  • 方便:为方便取值,页面组件的name要和对应路由name保持一致,直接获取路由的name即可
  • 管理:借助pinia进行管理
  • 缺点:组件需要手动设置name且和路由一致,麻烦点
  • 1、路由:/router/index.js
const routes = [
  {
    path: '/',
    component: () => import('@/views/home/index.vue'),
    name: 'home',//这里和component组件文件的defineOptions({ name: 'home'})保持一致
    meta: {
      title: '首页',
      keepAlive: true,//是否缓存
    },
  },
]
const router = createRouter({
  history: createWebHashHistory(),
  routes,
})
  • 2、App.vue
<template>
  <router-view v-slot="{ Component, route }">
    <keep-alive :include="appStore.cacheList">
      <component :is="Component" :key="route.fullPath" />
    </keep-alive>
  </router-view>
</template>
<script setup>
import { useAppStore } from '@/store/app'

const router = useRouter()
const appStore = useAppStore()

// 初始化缓存列表:根据路由配置添加需要缓存的组件
onMounted(() => {
  appStore.initCacheList(router)
})
</script>
<style scoped></style>
  • 3、pinia实现控制逻辑:/store/app.js
import { defineStore } from 'pinia'

/**
 * 应用状态管理 Store
 * 主要用于管理 keep-alive 缓存列表
 */
export const useAppStore = defineStore('app', {
    state: () => ({
        // 缓存列表,存储需要缓存的组件名称
        // keep-alive 的 include 需要匹配组件的 name
        cacheList: []
    }),

    getters: {
        /**
         * 获取缓存列表(只读)
         */
        getCacheList: (state) => {
            return [...state.cacheList]
        },

        /**
         * 检查某个组件是否在缓存列表中
         */
        isCached: (state) => {
            return (componentName) => {
                return state.cacheList.includes(componentName)
            }
        }
    },

    actions: {
        /**
         * 初始化缓存列表:根据路由配置添加需要缓存的组件
         * @param {Object} router - Vue Router 实例
         */
        initCacheList(router) {
            if (!router) {
                console.warn('initCacheList: router 参数不能为空')
                return
            }

            router.getRoutes().forEach(route => {
                if (route.meta?.keepAlive && route.name) {
                    // keep-alive 的 include 需要匹配组件的 name
                    // 这里使用路由 name,需要确保路由 name 和组件 defineOptions 中的 name 一致
                    const componentName = route.name
                    if (!this.cacheList.includes(componentName)) {
                        this.cacheList.push(componentName)
                    }
                }
            })
        },

        /**
         * 添加组件到缓存列表
         * @param {string} componentName - 组件名称
         */
        addCache(componentName) {
            if (!componentName) {
                console.warn('addCache: componentName 参数不能为空')
                return
            }

            if (!this.cacheList.includes(componentName)) {
                this.cacheList.push(componentName)
                console.log(`已添加 ${componentName} 到缓存列表`)
            }
        },

        /**
         * 从缓存列表中移除组件(临时取消缓存)
         * @param {string} componentName - 组件名称
         */
        removeCache(componentName) {
            if (!componentName) {
                console.warn('removeCache: componentName 参数不能为空')
                return
            }

            const index = this.cacheList.indexOf(componentName)
            if (index > -1) {
                this.cacheList.splice(index, 1)
                console.log(`已移除 ${componentName} 的缓存`)
            } else {
                console.warn(`未找到 ${componentName} 的缓存`)
            }
        },

        /**
         * 清空所有缓存
         */
        clearAllCache() {
            this.cacheList = []
        },

        /**
         * 根据路由信息自动管理缓存
         * 如果路由配置了 keepAlive 为 true,则自动添加到缓存列表
         * @param {Object} route - 路由对象
         */
        autoManageCache(route) {
            if (!route) return

            const componentName = route.name
            if (route.meta?.keepAlive && componentName) {
                if (!this.cacheList.includes(componentName)) {
                    this.addCache(componentName)
                }
            }
        }
    }
})

4.解决上面麻烦点

  • 动态赋值组件name为路由的name值
  • 1.在路由页面/router/index.js 使用工具处理
import { processRoutes } from '@/utils/route-helper';//自动为组件设置路由 name的辅助工具
const routes = [....];//如上
// 自动为所有路由组件设置 name(如果组件没有设置 name,则使用路由 name)
const processedRoutes = processRoutes(routes);

const router = createRouter({
  history: createWebHashHistory(),
  routes: processedRoutes,
})
export default router;
  • 2.@/utils/route-helper
/**
 * 路由辅助工具
 * 自动为组件设置路由 name,避免每个页面都手动设置 defineOptions
 */

import { defineComponent, h, markRaw } from 'vue'

/**
 * 包装路由组件,自动设置组件 name 为路由 name
 * @param {Function|Object} component - 组件导入函数或组件对象
 * @param {string} routeName - 路由名称
 * @returns {Function|Object} 包装后的组件
 */
export function withRouteName(component, routeName) {
    if (!routeName) {
        return component
    }

    // 如果是异步组件(函数)
    if (typeof component === 'function') {
        return () => {
            return component().then((module) => {
                const comp = module.default || module

                // 如果组件已经有 name,直接返回
                if (comp.name) {
                    return module
                }

                // 使用 defineComponent 包装组件,设置 name
                const wrappedComponent = defineComponent({
                    name: routeName,
                    setup(props, { slots, attrs }) {
                        // 渲染原组件
                        return () => h(comp, { ...props, ...attrs }, slots)
                    }
                })

                // 标记为原始对象,避免响应式
                markRaw(wrappedComponent)

                // 返回包装后的组件
                // 注意:需要先展开 module 再覆盖 default,否则 module.default 会把 wrappedComponent 覆盖掉
                return {
                    ...module,
                    default: wrappedComponent,
                }
            })
        }
    }

    // 如果是同步组件(对象)
    if (typeof component === 'object' && component !== null) {
        // 如果组件已经有 name,直接返回
        if (component.name) {
            return component
        }

        // 使用 defineComponent 包装组件,设置 name
        const wrappedComponent = defineComponent({
            name: routeName,
            ...component
        })

        markRaw(wrappedComponent)
        return wrappedComponent
    }

    return component
}

/**
 * 批量处理路由配置,自动为组件设置 name
 * @param {Array} routes - 路由配置数组
 * @returns {Array} 处理后的路由配置数组
 */
export function processRoutes(routes) {
    return routes.map(route => {
        // 如果有 name 和 component,则自动设置组件 name
        if (route.name && route.component) {
            route.component = withRouteName(route.component, route.name)
        }

        // 递归处理子路由
        if (route.children && Array.isArray(route.children)) {
            route.children = processRoutes(route.children)
        }

        return route
    })
}

以上已验证

  • 登录后 根据接口返回用户可访问的菜单信息 动态添加的路由 缓存功能同样适用