社区精选|vue 中 keep-alive 有个「大坑」你可能还不知道
今天小编为大家带来的是社区作者 wuwhs 的文章,让我们一起来学习Vue。
背景
背景是这样的,我们使用 vue2 开发一个在线客服使用的 IM 应用,基本布局是左边是访客列表,右边是访客对话,为了让对话加载更友好,我们将对话的路由使用<keep-alive>缓存起来。但是如果将所有对话都缓存,未必会造成缓存过多卡顿的问题。自然,就使用上了<keep-alive>提供的 max 属性,设置一个缓存对话内容组件上限,按照 LRU 算法,会销毁最旧访问的组件,保留最近使用的组件。
情景模拟
<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>
<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>
情景模拟结果
Vue2 中<keep-alive>组件实现原理
<keep-alive>LRU 算法
vue 会将 VNode 及组件实例(componentInstance)存到缓存(cache),cache 是一个 Object,同时还会维护一个 keys 队列;
根据 LRU 算法对 cache 和 keys 的管理:当前激活组件已存在缓存中,将组件对应 key 先删除,再插入的方式往前移动;
vue 会将 VNode 及组件实例(componentInstance)存到缓存(cache),cache 是一个 Object,同时还会维护一个 keys 队列;
根据 LRU 算法对 cache 和 keys 的管理:当前激活组件已存在缓存中,将组件对应 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)
}
}
<keep-alive>清理缓存函数实现
function pruneCacheEntry (
cache,
key,
keys,
current
) {
var cached$$1 = cache[key];
if (cached$$1 && (!current || cached$$1.tag !== current.tag)) {
cached$$1.componentInstance.$destroy();
}
cache[key] = null;
remove(keys, key);
}
<keep-alive>源码调试发现问题
解决方案
方案一:剪枝法
方案二:自定义清理缓存函数
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
}
}
})
},
vue3 中<KeepAlive>组件实现原理
vue3 中<KeepAlive>LRU 算法
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>清理缓存函数实现
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);
}
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;
}
}
总结
最后,在 vue2 中会出现<keep-alive>缓存相同名称组件,max 失效的问题,推荐使用自定义清理缓存函数,在获取组件实例基础上,对缓存实例销毁。下图是我在真实项目中优化的成果。完~
关注公众号:拾黑(shiheibook)了解更多
赞助链接:
关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
随时掌握互联网精彩
- 澳元兑换人民币汇率2024年3月27日
- 加币兑换人民币汇率2024年1月22日
- 韩元汇率对人民币2023年8月11日
- 哈啰顺风车:全年完单1.5亿次 四年来累计碳减排660万吨
- GSMA重申欧洲5G发展滞后警告,强调要公平分摊网络成本
- 手机业该如何面对增长平台期?
- hpv是什么病?
- 世界上第一个街机游戏;武汉大学建校;真空管的发明者诞生| 历史上的今天
- 2021 年度 IEEE Spectrum 编程语言排行榜 Top55:Python 处于“主宰”地位
- 《数据安全法》9月1日起正式实施,数据安全服务商机会何在?
- Microsoft Inspire China 合作伙伴集结令!尽享无限曝光与商机~
- 跳过腾讯独立研发!蓝洞《绝地求生》手游2首次曝光