嘿,vue中keep-alive有个「大坑」你可能还不知道

14,014 阅读5分钟

背景

背景是这样的,我们使用vue2开发一个在线客服使用的IM应用,基本布局是左边是访客列表,右边是访客对话,为了让对话加载更友好,我们将对话的路由使用<keep-alive>缓存起来。但是如果将所有对话都缓存,未必会造成缓存过多卡顿的问题。自然,就使用上了<keep-alive>提供的max属性,设置一个缓存对话内容组件上限,按照LRU算法,会销毁最旧访问的组件,保留最近使用的组件。本以为美好如期而至,直到上线后翻大车了,真实对话量大了,内存飙升卡顿。后来具体分析内存增长点,通过vuedevtool查看组件树,发现对话内容组件一直是递增,并非维持在max设置的数量上限!
各位看官稍安勿躁,下面就具体分析造成这个「大坑」的原理,已经解决它的方案。

情景模拟

为了方便模拟背景案例,这里就用vue2简单的写一个demo。
对话列表组件 APP.vue,点击列表中的某个访客,加载与访客对话内容。

<template>
  <div id="app">
    <section class="container">
      <aside class="aside">
        <ul>
          <li :class="{ active: active === index }" v-for="(user, index) in userList" :key="index"
            @click="selectUser(index, user)">
            {{ user.name }}
          </li>
        </ul>
      </aside>
      <section class="main">
        <keep-alive :max="3">
          <chat-content :key="currentUser.id" :user-info="currentUser"></chat-content>
        </keep-alive>
      </section>
    </section>
  </div>
</template>


<script>
import ChatContent from './views/ChatContent.vue';
export default {
components: {
ChatContent
},
data() {
return {
active: -1,
currentUser: {},
userList: [{ id: 1, name: "张三" },
{ id: 2, name: "李四" },
{ id: 3, name: "王五" },
{ id: 4, name: "老六" },
{ id: 5, name: "老八" },
{ id: 6, name: "老九" },
]
}
},
methods: {
selectUser(index) {
this.active = index
this.currentUser = this.userList[index];
}
},
}
</script>

<script> import ChatContent from './views/ChatContent.vue'; export default { components: { ChatContent }, data() { return { active: -1, currentUser: {}, userList: [{ id: 1, name: "张三" }, { id: 2, name: "李四" }, { id: 3, name: "王五" }, { id: 4, name: "老六" }, { id: 5, name: "老八" }, { id: 6, name: "老九" }, ] } }, methods: { selectUser(index) { this.active = index this.currentUser = this.userList[index]; } }, } </script>

这里使用keep-alive组件包裹的对话内容组件,需要加上key唯一标志,这样才会缓存相同名称(不同key)的组件,否则不会缓存。
对话内容组件ChatContent.vue,简单加一个计数器验证组件缓存了。

<template>
<div>
<h2>{{ userInfo.name }}</h2>
<h3>{{ num }}</h3>
<button @click="increament">+1</button>
</div>
</template>




<script>
export default {
props: {
userInfo: Object,
},




data() {
return {
num: 0,
};
},




methods: {
increament() {
this.num += 1;
},
},
};
</script>

methods: { increament() { this.num += 1; }, }, }; </script>

情景模拟结果

实验发现,虽然缓存组件个数上限max为3,实际是逐个缓存了全部内容组件,看来设置max属性失效了。

Vue2中<keep-alive>组件实现原理

为什么缓存相同名称的组件,max属性会失效呢?这里就要从Vue2<keep-alive>组件实现原理来看。

<keep-alive>LRU算法

  • vue会将VNode及组件实例(componentInstance)存到缓存(cache),cache是一个Object,同时还会维护一个keys队列;
  • 根据LRU算法对cachekeys的管理:当前激活组件已存在缓存中,将组件对应key先删除,再插入的方式往前移动;
  • 当前激活组件没有再缓存中,直接存入缓存,此时判断是否超过了缓存个数上限,如果超过了,使用pruneCacheEntry清理keys第一个位置(最旧)的组件对应的缓存。

    if (cache[key]) {
    vnode.componentInstance = cache[key].componentInstance;
    // make current key freshest
    remove(keys, key);
    keys.push(key);
    } else {
    cache[key] = vnode;
    keys.push(key);
    // prune oldest entry
    if (this.max && keys.length > parseInt(this.max)) {
    
    
    
    
    pruneCacheEntry(cache, keys[0], keys, this._vnode);
    console.log('cache: ', cache)
    console.log('keys: ', keys)
    }
    }

    pruneCacheEntry(cache, keys[0], keys, this._vnode); console.log('cache: ', cache) console.log('keys: ', keys) } }

    <keep-alive>清理缓存函数实现

    下面再来看清理缓存函数pruneCacheEntry的实现:比对当前传入组件和在缓存中的组件tag是否相同,如果不同,就去销毁组件实例,否则不销毁。

    function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cachekeys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vuedevtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3LRU算法实现思路一样,只不过cachekeys分别使用MapSet数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.typecurrent.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:propsrendersetup__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.typecurrent.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    。这样我们就可以通过function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    和function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    算法,根据function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    和function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    。这样我们就可以通过function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    和function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的function pruneCacheEntry (
    cache,
    key,
    keys,
    current
    ) {
    var cached1 = cache[key];
    if (cached1 && (!current || cached1.tag !== current.tag)) {
      cached1.componentInstance.destroy();
    }
    cache[key] = null;
    remove(keys, key);
    }</code></pre><p>看到这里似乎也没有毛病,究竟是哪里出问题了呢?</p><h3>&lt;keep-alive&gt;源码调试发现问题</h3><p>不妨我们打印一下<code>cache</code>(<code>VNode</code>缓存)和<code>keys</code><br><br>发现也没什么问题,按照<code>URL</code>算法得到正确结果。<br>再看看清理缓存函数里<code>cached$$1.tag</code>和<code>current.tag</code>的打印<br><br>真相了!他两由于组件名称相同,导致相等,没有进入销毁组件实例的判断里,这就是问题来源!为什么针对相同组件名称不去销毁实例呢?可能是为了某些情景下组件复用吧。</p><h2>解决方案</h2><p>既然问题症结我们已经找到,从源头上去解决问题当然最佳,但是现实是<code>vue2</code>源码层面是没有去解决的(<code>vue3</code>有解决,这个后面再说),只能从我们应用侧再去想想办法。这里我想到的有两种方案。</p><h3>方案一:剪枝法</h3><p>维护一个全局状态(比如<code>vuex</code>)对话<code>ids</code>队列,最大长度为<code>max</code>,类似<code>vue</code>中<code>LRU</code>算法中的<code>keys</code>,在组件<code>activated</code>钩子函数触发时更新<code>ids</code>队列。对话内容组件的子组件判断当前对话<code>id</code>是否在<code>ids</code>队列中,不在那么就会<code>v-if</code>剔除,否则缓存起来,这样很大程度程度上释放缓存。类似剪去树的枝丫,减轻重量,这里叫做「剪枝法」好了。</p><h3>方案二:自定义清理缓存函数</h3><p>我们不再使用<code>keep-alive</code>提供的<code>max</code>属性来清理缓存,让其将组件实例全部缓存下来,当前激活组件,<code>activated</code>钩子函数触发,此时通过<code>this.vnode.parent.componentInstance获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    获取组件实例,进而可以获取挂载在上面的cache和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    和keys。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    。这样我们就可以通过LRU算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    算法,根据key自定义精准清理缓存了。
    
    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },
    
    下面对照 vue的devtool工具查看效果
    
    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。
    
    vue3中<KeepAlive>组件实现原理
    
    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。
    
    没有冗余缓存组件,奈斯!
    
    vue3中<KeepAlive>LRU算法
    
    vue3中LRU算法实现思路一样,只不过cache和keys分别使用Map和Set数据结构实现,数据更干净简洁。
    
    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }
    
    vue3中<KeepAlive>清理缓存函数实现
    
    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。
    
    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }
    
    再来看看cache.type和current.type到底是什么
    
    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:props、render、setup、__hmrId等。
    
    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }
    
    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.type和current.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。
    
    总结
    
    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
    自定义精准清理缓存了。

    activated() {
    const { cache, keys } = this.$vnode.parent.componentInstance;
    console.log('activated cache: ', cache)
    console.log('activated keys: ', keys)
    
    
    
    
    let cacheLen = 0
    const max = 3
    Object.keys(cache).forEach(key => {
    if (cache[key]) {
    cacheLen += 1
    if (cacheLen > max) {
    const key = keys.shift()
    cache[key].componentInstance.$destroy()
    cache[key] = null
    }
    }
    })
    },

    let cacheLen = 0 const max = 3 Object.keys(cache).forEach(key => { if (cache[key]) { cacheLen += 1 if (cacheLen > max) { const key = keys.shift() cache[key].componentInstance.$destroy() cache[key] = null } } }) },

    下面对照 vuedevtool工具查看效果

    完全符合预期!方式二从<keep-alive>组件根就清理了缓存组件,更彻底,对业务代码侵染性也更小。
    你以为这样就完了?上面我还提到在vue3中已经解决了这个问题。

    vue3中<KeepAlive>组件实现原理

    话不多说,先来看上面相同的案例在使用vue3写的效果如何呢?这里就不“重复”贴代码了,直接看devtool组件树的表现。

    没有冗余缓存组件,奈斯!

    vue3中<KeepAlive>LRU算法

    vue3LRU算法实现思路一样,只不过cachekeys分别使用MapSet数据结构实现,数据更干净简洁。

    const cache = new Map();
    const keys = new Set();
    // ...
    if (cachedVNode) {
    // copy over mounted state
    vnode.el = cachedVNode.el;
    
    
    
    
    // ...
    // make this key the freshest
    keys.delete(key);
    keys.add(key);
    }
    else {
    keys.add(key);
    // prune oldest entry
    if (max && keys.size > parseInt(max, 10)) {
    pruneCacheEntry(keys.values().next().value);
    }
    }

    // ... // make this key the freshest keys.delete(key); keys.add(key); } else { keys.add(key); // prune oldest entry if (max && keys.size > parseInt(max, 10)) { pruneCacheEntry(keys.values().next().value); } }

    vue3中<KeepAlive>清理缓存函数实现

    vue3中清理组件实例缓存函数也是pruneCacheEntry,不同的是,比对当前传入组件和在缓存中的组件tag是否相同,决定是否销毁组件实例。

    function pruneCacheEntry(key) {
    const cached = cache.get(key);
    if (!current || cached.type !== current.type) {
    unmount(cached);
    }
    else if (current) {
    // current active instance should no longer be kept-alive.
    // we can't unmount it now but it might be later, so reset its flag now.
    resetShapeFlag(current);
    }
    cache.delete(key);
    keys.delete(key);
    }

    再来看看cache.typecurrent.type到底是什么

    对比我们会发现,不再是简单的组件名称字符标志,而是一个对象描述,包含了很多属性,因为在初始化组件实例时,会给每个实例加上属性:propsrendersetup__hmrId等。

    function initProps(instance, rawProps, isStateful, isSSR = false) {
    const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = /* @PURE */ Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    // ...
    
    instance.attrs = attrs;
    }
    function isInHmrContext(instance) {
    while (instance) {
    if (instance.type.__hmrId)
    return true;
    instance = instance.parent;
    }
    }

    即使是对象中所有属性相同,但是对象不是同一个引用地址,造成cache.typecurrent.type不相等,因此会销毁实例对象unmount(cached)。以上就是vue3对这个问题解决方案。

    总结

    最后,在vue2中会出现<keep-alive>缓存相同名称组件,max失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~

本文来源:嘿,vue中keep-alive有个「大坑」你可能还不知道