项目精讲 - keepAlive

195 阅读3分钟

<keepAlive> 包裹动态组件时,可以将组件缓存起来。例如我们要浏览商品,可能点击详情后返回仍然在当前位置,不进行更新,这时候就要用到他。

我们首先想到的肯定是可以用他将需要缓存的子组件包裹起来就行了,但是这样在只有几个小页面中的项目可行,项目大了就复杂且难以维护。

我们可以通过路由中配置一个自定义meta属性来决定是否缓存。

  1. 我们可以在根组件中使用v-if的方式来进行判断组件是否缓存。

1.1

<keep-alive>
  <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>

1.2

<router-view v-slot="{ Component, route }"> // 这里解构出路由对象和组件对象,不是插槽,是新用法
  <keep-alive>
    <component :is="Component" v-if="route.meta.keepAlive"/>
  </keep-alive>
  <component :is="Component" v-if="!route.meta.keepAlive"/>
</router-view>
  1. 通过 keepAlive自带的属性进行绑定。 他有三个属性可以进行设置

85013324869350c2c362f87b0f4427c.jpg

我们通过include属性来进行一个动态绑定。当我们是从进入详情页面返回的时候,我们不进行缓存,当我们从最初页面平级跳转,我们清除缓存。

我们现在组件使用给keepAlive包裹一下:

<template>
  <router-view v-slot="{ Component }">
    <keep-alive :include="cachedComponents">
      <component :is="Component" />
    </keep-alive>
  </router-view>
</template>

<script setup>
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'

const cachedComponents = ref([])
const route = useRoute()

watch(route, (newVal,old) => {
  if (newVal.meta.keepAlive && !cachedComponents.value.includes(newVal.name)) {
     if(newVal.meta.deep > )
    cachedComponents.value.push(newVal.name)
  }
})
</script>

我们先来设置一下路由:

// router/index.ts
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/list',
    name: 'List',
    component: () => import('@/views/List.vue'),
    meta: {
      depth: 1 // 添加层级标记
    }
  },
  {
    path: '/detail/:id',
    name: 'Detail',
    component: () => import('@/views/Detail.vue'),
    meta: {
      depth: 2
    }
  },
  {
    path: '/settings',
    name: 'Settings',
    component: () => import('@/views/Settings.vue'),
    meta: {
      depth: 1
    }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

// 缓存管理逻辑
const cachePages = ref<string[]>([])

router.beforeEach((to, from) => {
  // 判断是否是前进更深层级
  const isEnteringDeeper = (to.meta.depth as number) > (from.meta.depth as number)
  
  // 判断是否是返回操作
  const isGoingBack = (to.meta.depth as number) < (from.meta.depth as number)

  if (isEnteringDeeper) {
    // 进入更深层级时缓存前一个页面
    if (!cachePages.value.includes(from.name as string)) {
      cachePages.value.push(from.name as string)
    }
  } else if (!isGoingBack) {
    // 平级切换时移除前一个页面的缓存
    const index = cachePages.value.indexOf(from.name as string)
    if (index > -1) {
      cachePages.value.splice(index, 1)
    }
  }
})

keepAlive有两个生命周期钩子,onactived 和 onDeactived。 当我们的被缓存的页面显示时,会触发onactived钩子,隐藏时会触发 onDeactived钩子,触发顺序为 onCreated onMounted on Deactived。

路由守卫会在onDeactived之前执行。确保一些逻辑的完成。

我们可以利用onactived钩子来进行一些数据的更新。

问题:

  1. 你能详细描述的内部工作原理吗?它是如何实现组件缓存的?  内部维护了一个 cache 对象(存储组件实例)和一个 keys 链表: 主要是编译时候获取到keepAlive包裹的组件,接着使用组件ID和tag去生成一个key。查看是否已经存在这个组件。缓存过就取出这个组件实例。

因为缓存过多会导致性能问题,所以keepAlive有一个max属性。他是使用了一个LRU缓存的算法。会删除我们最久没有访问到的组件。 LRU缓存他是一个算法,当你使用到了这个数据,比如get和put,就不算最久未使用的了,我们先维护了一个双向链表,这样可以在删除的时候达到O1的时间复杂度。

  1. Vue3中的与Vue2相比有什么变化?
  • 在Vue3中,这两个属性支持更灵活的匹配模式:字符串形式 (逗号分隔) 正则表达式
  • Vue3中对max属性的处理算法进行了优化,当缓存实例超过max设定值时,会自动删除最久未使用的组件
  1. 你是如何处理缓存组件中的数据更新问题的?
  •   控制页面的层级缓存。
    
  •   ondeactive更新数据。
    
  •   监听路由变化更新。
    
  1. 如果需要手动控制组件的缓存状态(如强制刷新某个缓存页面),你会如何实现?
  •   总部pinia来管理include数组。必要时候更改数组。