记一次Keep-Alive与router-view,加key有大坑

记一次Keep-Alive与router-view,加key有大坑

背景:

我在做Keep-Alive结合Router-View进行页面级的组件缓存时,缓存的数据在页面切换后没有保留。同时每次路由切换,onMounted和onActivated都会执行。

代码如下:

<router-view v-slot="{ Component }" :key="route.path">
          <keep-alive>
            <component class="view" :is="Component"/>
          </keep-alive>
</router-view>
排查思路:

首先我去官网抄了一段KeepAlive的示例代码,放到了RouterView的外层,发现并无问题。显然是我RouterView的使用方法有误。

官网示例:

<script setup>
import { shallowRef } from 'vue'
import CompA from './CompA.vue'
import CompB from './CompB.vue'
// 进行浅层代理,避免不必要的花销
const current = shallowRef(CompA)
</script>

<template>
  <div class="demo">
    <!--切换时,current.value值会发生改变  -->
    <label><input type="radio" v-model="current" :value="CompA" /> A</label>
    <label><input type="radio" v-model="current" :value="CompB" /> B</label>
    <KeepAlive>
      <component :is="current"></component>
    </KeepAlive>
  </div>
</template>

于是在经过一系列的排查,最后通过控制变量法,发现是RouterView绑定了Key,导致每次都会重复刷新。这是我的原始写法,把key绑定在了RouterView上。但是我残留的记忆依稀记得,Vue2是可以这样用的。让我对比一下Vue2项目和Vue3项目的Keep-alive+route-view写法。

Vue3:

        <router-view :key="route.path" v-slot="{ Component }">
          <keep-alive>
            <component class="view" :is="Component" />
          </keep-alive>
        </router-view>

Vue2:

<!-- router-view加上Key可以解决不同路由,但是相同组件,页面不刷新问题 -->
<keep-alive>
  <router-view :key="routeKey" />
</keep-alive>

发现大家都是使用Keep-alive,Vue3 会采用Slot的形式,把KeepAlive加在Component的外层,而Vue2会直接把KeepAlive包裹在RouterView的外层。那就很有意思了,那我试一下,在Vue3中把KeepAlive标签也加到RouterView的外层,同时不给router-view加key试试,不出意料,当然是不行的。

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

我在几个重要位置中打了断点,在进行路由的切换时发现这种写法在Vue3中会导致获取rawVNode时,rawVNode全部指向了RouterView,这样导致了KeepAlive在缓存组件实例时,并没有缓存到对应的Component,所以这种写法无疑是不可以使用的,官网也不推荐这种写法。

在这里插入图片描述

那为什么Vue2可以呢?为此我也在Vue2的项目中打了断点,发现即使,KeepAlive组件中包裹着RouterView,但是在Vue2的this.$slots依旧获取的是业务组件的Vue实例。这里我估计是RouterView的具体实现有关,便不细究下去,有懂行的小伙伴可以说说~

在这里插入图片描述

最后让我们解决我最初的问题:
为什么我在RouterView标签上加上了key,就导致KeepAlive不生效呢?

在我的调试下,发现当我加上key时,keepAlive组件在进行路由跳转时,每次都会执行setup函数的初始化流程,即每次都初始化了KeepAlive这个Vue组件,正好印证了“router-view加上Key可以解决不同路由,但是相同组件,页面不刷新问题”。

用这个组件里面的一些核心代码而言就是每次都会重置cache,keys这两个关键内存值,导致KeepAlive失效。

在这里插入图片描述

解决方案:

我们可以把key放在component组件上面,这样KeepAlive源码中,Vnode.key就拥有了唯一的值,Cache这个Map也就可以正常进行缓存了。

 <router-view v-slot="{ Component }" >
          <keep-alive>
            <component class="view" :is="Component" :key="route.path" v-if="route.meta?.keepAlive"/>
          </keep-alive>
          <component class="view" :is="Component" :key="route.path"  v-if="!route.meta?.keepAlive"/>
 </router-view>
拓展:KeepAlive组件渲染解读
const KeepAliveImpl: ComponentOptions = {
  name: `KeepAlive`,

  // Marker for special handling inside the renderer. We are not using a ===
  // check directly on KeepAlive in the renderer, because importing it directly
  // would prevent it from being tree-shaken.
  __isKeepAlive: true,

  props: {
    include: [String, RegExp, Array],
    exclude: [String, RegExp, Array],
    max: [String, Number],
  },

  setup(props: KeepAliveProps, { slots }: SetupContext) {
------------------------------------这部分除非刷新,只会在使用KeepAlive组件时执行一次---------------------------------------
    const instance = getCurrentInstance()!
    const cache: Cache = new Map()
    const keys: Keys = new Set()
   	...
    const cacheSubtree = () => {
      // fix #1621, the pendingCacheKey could be 0
      if (pendingCacheKey != null) {
        cache.set(pendingCacheKey, getInnerChild(instance.subTree))
      }
    }
    onMounted(cacheSubtree)
    onUpdated(cacheSubtree)
--------这部分作为Setup的返回函数,会返回一个VNode实例,这个实例可能读缓存,也可能是rawNode,在KeepAlive组件内部的Component改变时会执行----------
    return () => {
     
      const rawVNode = children[0]
   	  ...
      let vnode = getInnerChild(rawVNode)
      const key = vnode.key == null ? comp : vnode.key
      const cachedVNode = cache.get(key)
      return isSuspense(rawVNode.type) ? rawVNode : vnode
    }
  },
  
}


Vue3中的Router-View Keep-Alive是一个非常实用且强大的功能。在Vue3中,Router-View Keep-Alive用于在Vue3应用中缓存组件实例,从而避免了不必要的渲染和载。当我们在Vue3程序中使用Keep-Alive标签时,就可以在缓存组件渲染之前将组件实例保留在内存中。这就意味着当我们再次访问同一组件时,不必重新创建组件实例,从而节省了不必要的性能开销。 在Vue3的Router-View Keep-Alive中,缓存策略有两种:全局缓存和局部缓存。全局缓存是指整个应用程序中的所有组件都经过缓存,而局部缓存是指仅将所需组件进行缓存。对于需要局部缓存的组件,可以使用keep-alive进行标。 Vue3的Router-View Keep-Alive还有另一个非常实用的功能,即通过keep-alive组件传递属性。通过keep-alive组件能够将属性传递给被缓存的组件,并且在缓存期间能够保留原属性的状态。这样可以保证在组件再次被激活时,能够保留原有的属性状态。 总之,在Vue3应用程序中,Router-View Keep-Alive是非常实用的功能,并且能够大大提高系统性能。通过使用Router-View Keep-Alive,我们可以将应用程序中的组件缓存起来,从而避免不必要的渲染和载。此外,还能够通过keep-alive组件传递属性,保留原有属性状态,从而提高组件的可重用性。因此,尽管这不是Vue3中最醒目的功能之一,但它确实非常重要,对于Vue3应用程序的性能和可维护性都具有重要意义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值